├── students ├── dimdiden │ ├── my.db │ ├── map.yaml │ ├── map.json │ ├── urlshort │ │ ├── pair.go │ │ ├── handler.go │ │ └── bold.go │ └── main.go ├── hackeryarn │ ├── hackeryarn │ ├── urls.yaml │ ├── urls.json │ ├── main_test.go │ ├── main.go │ └── urlshort │ │ ├── handler.go │ │ └── handler_test.go ├── liikt │ ├── storage │ │ ├── map.json │ │ └── map.yml │ ├── main │ │ └── main.go │ └── handler.go ├── dennisvis │ ├── paths.yml │ ├── paths.json │ ├── main.go │ └── urlshort │ │ └── handler.go ├── baltuky │ ├── redirect.yaml │ └── src │ │ ├── main.go │ │ └── urlshort │ │ └── handler.go ├── README.md ├── kalexmills │ ├── main │ │ └── main.go │ └── urlshort.go ├── latentgenius │ ├── main │ │ └── main.go │ └── handler.go ├── emrekp │ └── handler_impls.go ├── teimurjan │ ├── main.go │ └── handler.go └── rnbdev │ ├── main.go │ └── handler.go ├── .gitignore ├── main └── main.go ├── handler.go └── README.md /students/dimdiden/my.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophercises/urlshort/HEAD/students/dimdiden/my.db -------------------------------------------------------------------------------- /students/hackeryarn/hackeryarn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gophercises/urlshort/HEAD/students/hackeryarn/hackeryarn -------------------------------------------------------------------------------- /students/liikt/storage/map.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"path": "/yt", "url":"https://www.youtube.com/"}, 3 | {"path": "/tr", "url":"http://play.typeracer.com/"} 4 | ] 5 | -------------------------------------------------------------------------------- /students/dennisvis/paths.yml: -------------------------------------------------------------------------------- 1 | - path: /urlshort 2 | url: https://github.com/gophercises/urlshort 3 | - path: /urlshort-final 4 | url: https://github.com/gophercises/urlshort/tree/solution -------------------------------------------------------------------------------- /students/dimdiden/map.yaml: -------------------------------------------------------------------------------- 1 | - path: /urlshort 2 | url: https://github.com/gophercises/urlshort 3 | - path: /urlshort-final 4 | url: https://github.com/gophercises/urlshort/tree/solution 5 | -------------------------------------------------------------------------------- /students/baltuky/redirect.yaml: -------------------------------------------------------------------------------- 1 | - path: "/urlshort" 2 | url: "https://github.com/gophercises/urlshort" 3 | - path: "/urlshort-final" 4 | url: "https://github.com/gophercises/urlshort/tree/final" -------------------------------------------------------------------------------- /students/hackeryarn/urls.yaml: -------------------------------------------------------------------------------- 1 | - path: /urlshort 2 | url: https://github.com/gophercises/urlshort 3 | - path: /urlshort-final 4 | url: https://github.com/gophercises/urlshort/tree/solution 5 | -------------------------------------------------------------------------------- /students/liikt/storage/map.yml: -------------------------------------------------------------------------------- 1 | - path: /urlshort 2 | url: https://github.com/gophercises/urlshort 3 | - path: /urlshort-final 4 | url: https://github.com/gophercises/urlshort/tree/solution 5 | -------------------------------------------------------------------------------- /students/dimdiden/map.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/mobile", 4 | "url" : "https://mobile-review.com/" 5 | }, 6 | { 7 | "path": "/pikabu", 8 | "url" : "https://pikabu.ru/" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /students/dennisvis/paths.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/urlshort", 4 | "url": "https://github.com/gophercises/urlshort" 5 | }, 6 | { 7 | "path": "/urlshort-final", 8 | "url": "https://github.com/gophercises/urlshort/tree/solution" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /students/hackeryarn/urls.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/urlShort", 4 | "url": "https://github.com/gophercises/urlshort" 5 | }, 6 | { 7 | "path": "/urlshortFinal", 8 | "url": "https://github.com/gophercises/urlshort/tree/solution" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /students/README.md: -------------------------------------------------------------------------------- 1 | # Student Examples 2 | 3 | The following are example implementations submitted by students/gophers learning from this course. 4 | 5 | The primary purposes of these examples are: 6 | 7 | 1. To provide example implementations to discuss and review when recording the screencasts for this course. 8 | 2. To provide a way for students to contribute to this course. 9 | -------------------------------------------------------------------------------- /students/dimdiden/urlshort/pair.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | // Pair is the main structure used in the package logic 11 | type Pair struct { 12 | // Path is a short url representation 13 | Path string 14 | // Url is full url address 15 | Url string 16 | } 17 | 18 | // PairProducer is the general interface to produce array of Pair structs 19 | type PairProducer interface { 20 | Pair() ([]Pair, error) 21 | } 22 | 23 | // Content is used to give []byte the possibility of implementing PairProducer 24 | type Content []byte 25 | 26 | // Pair tries to parse either yaml or json content 27 | // and converts it to array of Pair structs. If non of the available 28 | // umarshaling methods succeed, a simple error is returned 29 | func (c Content) Pair() ([]Pair, error) { 30 | var pairs []Pair 31 | var err error 32 | 33 | if err = yaml.Unmarshal(c, &pairs); err == nil { 34 | return pairs, nil 35 | } 36 | if err = json.Unmarshal(c, &pairs); err == nil { 37 | return pairs, nil 38 | } 39 | return nil, fmt.Errorf("Could not unmarshal file. Available formats: json or yaml: %v", err) 40 | } 41 | -------------------------------------------------------------------------------- /students/baltuky/src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "urlshort" 7 | "log" 8 | "flag" 9 | ) 10 | 11 | func main() { 12 | yamlFilename := flag.String("yaml-file", "redirect.yaml", "Yaml file name with redirection URLs") 13 | flag.Parse() 14 | 15 | mux := defaultMux() 16 | 17 | mapHandler := urlshort.NewHttpRedirectHandler( 18 | urlshort.NewBaseUrlMapper(map[string]string{ 19 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 20 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 21 | }), mux) 22 | 23 | yamlUrlMapper, err := urlshort.NewYamlUrlMapper(*yamlFilename) 24 | if err != nil { 25 | log.Fatalf("Can't create YAML redirect URL provider. %v", err) 26 | } 27 | 28 | yamlHandler := urlshort.NewHttpRedirectHandler(yamlUrlMapper, mapHandler) 29 | 30 | fmt.Println("Starting the server on :8080") 31 | http.ListenAndServe(":8080", yamlHandler) 32 | } 33 | 34 | func defaultMux() *http.ServeMux { 35 | mux := http.NewServeMux() 36 | mux.HandleFunc("/", hello) 37 | return mux 38 | } 39 | 40 | func hello(w http.ResponseWriter, r *http.Request) { 41 | fmt.Fprintln(w, "Hello, world!") 42 | } 43 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gophercises/urlshort" 8 | ) 9 | 10 | func main() { 11 | mux := defaultMux() 12 | 13 | // Build the MapHandler using the mux as the fallback 14 | pathsToUrls := map[string]string{ 15 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 16 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 17 | } 18 | mapHandler := urlshort.MapHandler(pathsToUrls, mux) 19 | 20 | // Build the YAMLHandler using the mapHandler as the 21 | // fallback 22 | yaml := ` 23 | - path: /urlshort 24 | url: https://github.com/gophercises/urlshort 25 | - path: /urlshort-final 26 | url: https://github.com/gophercises/urlshort/tree/solution 27 | ` 28 | yamlHandler, err := urlshort.YAMLHandler([]byte(yaml), mapHandler) 29 | if err != nil { 30 | panic(err) 31 | } 32 | fmt.Println("Starting the server on :8080") 33 | http.ListenAndServe(":8080", yamlHandler) 34 | } 35 | 36 | func defaultMux() *http.ServeMux { 37 | mux := http.NewServeMux() 38 | mux.HandleFunc("/", hello) 39 | return mux 40 | } 41 | 42 | func hello(w http.ResponseWriter, r *http.Request) { 43 | fmt.Fprintln(w, "Hello, world!") 44 | } 45 | -------------------------------------------------------------------------------- /students/kalexmills/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/kalexmills/gophercises/urlshort" 8 | ) 9 | 10 | func main() { 11 | mux := defaultMux() 12 | 13 | // Build the MapHandler using the mux as the fallback 14 | pathsToUrls := map[string]string{ 15 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 16 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 17 | } 18 | mapHandler := urlshort.MapHandler(pathsToUrls, mux) 19 | 20 | // Build the YAMLHandler using the mapHandler as the 21 | // fallback 22 | yaml := ` 23 | pairs: 24 | - path: /urlshort 25 | url: https://github.com/gophercises/urlshort 26 | - path: /urlshort-final 27 | url: https://github.com/gophercises/urlshort/tree/solution` 28 | yamlHandler, err := urlshort.YAMLHandler([]byte(yaml), mapHandler) 29 | if err != nil { 30 | panic(err) 31 | } 32 | fmt.Println("Starting the server on :8080") 33 | http.ListenAndServe(":8080", yamlHandler) 34 | } 35 | 36 | func defaultMux() *http.ServeMux { 37 | mux := http.NewServeMux() 38 | mux.HandleFunc("/", hello) 39 | return mux 40 | } 41 | 42 | func hello(w http.ResponseWriter, r *http.Request) { 43 | fmt.Fprintln(w, "Hello, world!") 44 | } 45 | -------------------------------------------------------------------------------- /students/latentgenius/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/gophercises/urlshort/students/latentgenius" 8 | ) 9 | 10 | func main() { 11 | mux := defaultMux() 12 | 13 | // Build the MapHandler using the mux as the fallback 14 | pathsToUrls := map[string]string{ 15 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 16 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 17 | } 18 | mapHandler := latentgenius.MapHandler(pathsToUrls, mux) 19 | 20 | // Build the YAMLHandler using the mapHandler as the 21 | // fallback 22 | yaml := ` 23 | - path: /urlshort 24 | url: https://github.com/gophercises/urlshort 25 | - path: /urlshort-final 26 | url: https://github.com/gophercises/urlshort/tree/final 27 | ` 28 | yamlHandler, err := latentgenius.YAMLHandler([]byte(yaml), mapHandler) 29 | if err != nil { 30 | panic(err) 31 | } 32 | fmt.Println("Starting the server on :8080") 33 | http.ListenAndServe(":8080", yamlHandler) 34 | } 35 | 36 | func defaultMux() *http.ServeMux { 37 | mux := http.NewServeMux() 38 | mux.HandleFunc("/", hello) 39 | return mux 40 | } 41 | 42 | func hello(w http.ResponseWriter, r *http.Request) { 43 | fmt.Fprintln(w, "Hello, world!") 44 | } 45 | -------------------------------------------------------------------------------- /students/baltuky/src/urlshort/handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "net/http" 5 | "gopkg.in/yaml.v2" 6 | "io/ioutil" 7 | "log" 8 | ) 9 | 10 | func NewBaseUrlMapper(urls map[string]string) func(string) (string, bool) { 11 | return func(path string) (string, bool) { 12 | url, ok := urls[path] 13 | return url, ok 14 | } 15 | } 16 | 17 | func NewYamlUrlMapper(filename string) (func(string) (string, bool), error) { 18 | content, err := ioutil.ReadFile(filename) 19 | 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | yml := []map[string]string{} 25 | err = yaml.Unmarshal(content, &yml) 26 | 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | mapping := make(map[string]string) 32 | 33 | for _, m := range yml { 34 | mapping[m["path"]] = m["url"] 35 | } 36 | 37 | return NewBaseUrlMapper(mapping), nil 38 | } 39 | 40 | func NewHttpRedirectHandler(mapper func(string) (string, bool), fallback http.Handler) http.HandlerFunc { 41 | return func(w http.ResponseWriter, r *http.Request) { 42 | if url, ok := mapper(r.URL.Path); ok { 43 | log.Printf("Redirecting %s to %s\n", r.URL.Path, url) 44 | http.Redirect(w, r, url, http.StatusMovedPermanently) 45 | } else { 46 | fallback.ServeHTTP(w, r) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // MapHandler will return an http.HandlerFunc (which also 8 | // implements http.Handler) that will attempt to map any 9 | // paths (keys in the map) to their corresponding URL (values 10 | // that each key in the map points to, in string format). 11 | // If the path is not provided in the map, then the fallback 12 | // http.Handler will be called instead. 13 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 14 | // TODO: Implement this... 15 | return nil 16 | } 17 | 18 | // YAMLHandler will parse the provided YAML and then return 19 | // an http.HandlerFunc (which also implements http.Handler) 20 | // that will attempt to map any paths to their corresponding 21 | // URL. If the path is not provided in the YAML, then the 22 | // fallback http.Handler will be called instead. 23 | // 24 | // YAML is expected to be in the format: 25 | // 26 | // - path: /some-path 27 | // url: https://www.some-url.com/demo 28 | // 29 | // The only errors that can be returned all related to having 30 | // invalid YAML data. 31 | // 32 | // See MapHandler to create a similar http.HandlerFunc via 33 | // a mapping of paths to urls. 34 | func YAMLHandler(yml []byte, fallback http.Handler) (http.HandlerFunc, error) { 35 | // TODO: Implement this... 36 | return nil, nil 37 | } 38 | -------------------------------------------------------------------------------- /students/dimdiden/urlshort/handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // MapHandler will return an http.HandlerFunc (which also 9 | // implements http.Handler) that will attempt to map any 10 | // paths (keys in the map) to their corresponding URL (values 11 | // that each key in the map points to, in string format). 12 | // If the path is not provided in the map, then the fallback 13 | // http.Handler will be called instead. 14 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 15 | return func(w http.ResponseWriter, r *http.Request) { 16 | if val, ok := pathsToUrls[r.URL.String()]; ok { 17 | fmt.Println("Mached: ", val) 18 | http.Redirect(w, r, val, 301) 19 | return 20 | } else { 21 | fallback.ServeHTTP(w, r) 22 | } 23 | } 24 | } 25 | 26 | // MainHandler works with PairProducer interface to get pairs, convert 27 | // them to map and invokes MapHandler with this map 28 | func MainHandler(pp PairProducer, fallback http.Handler) (http.HandlerFunc, error) { 29 | pairs, err := pp.Pair() 30 | if err != nil { 31 | return nil, err 32 | } 33 | pathMap := buildMap(pairs) 34 | return MapHandler(pathMap, fallback), nil 35 | } 36 | 37 | // buildMap will convert []PathMap to map[string]string 38 | func buildMap(pairs []Pair) map[string]string { 39 | resultMap := make(map[string]string) 40 | for _, p := range pairs { 41 | resultMap[p.Path] = p.Url 42 | } 43 | return resultMap 44 | } 45 | -------------------------------------------------------------------------------- /students/hackeryarn/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type flaggerMock struct { 9 | stringVarCalls int 10 | varNames []string 11 | varUsages []string 12 | varStringValues []string 13 | } 14 | 15 | func (f *flaggerMock) StringVar(p *string, name, value, usage string) { 16 | f.stringVarCalls++ 17 | f.varNames = append(f.varNames, name) 18 | f.varStringValues = append(f.varStringValues, value) 19 | f.varUsages = append(f.varUsages, usage) 20 | } 21 | 22 | func TestConfigFlags(t *testing.T) { 23 | flagger := &flaggerMock{} 24 | 25 | ConfigFlags(flagger) 26 | 27 | if flagger.stringVarCalls != 2 { 28 | t.Error("it should set string vars") 29 | } 30 | 31 | assertFlags(t, flagger) 32 | } 33 | 34 | func assertFlags(t *testing.T, flagger *flaggerMock) { 35 | t.Helper() 36 | 37 | expectedNames := []string{YAMLFlag, JSONFlag} 38 | expectedUsages := []string{YAMLFlagUsage, JSONFlagUsage} 39 | expectedStringValues := []string{YAMLFlagValue, JSONFlagValue} 40 | 41 | if !reflect.DeepEqual(expectedNames, flagger.varNames) { 42 | t.Errorf("it should setup flag names to be %v, got %v", 43 | expectedNames, flagger.varNames) 44 | } 45 | 46 | if !reflect.DeepEqual(expectedUsages, flagger.varUsages) { 47 | t.Errorf("it should setup flag usages to be %v, got %v", 48 | expectedUsages, flagger.varUsages) 49 | } 50 | 51 | if !reflect.DeepEqual(expectedStringValues, flagger.varStringValues) { 52 | t.Errorf("it should setup string values to be %v, got %v", 53 | expectedStringValues, flagger.varStringValues) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /students/liikt/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/gophercises/urlshort/students/liikt" 10 | ) 11 | 12 | func main() { 13 | ymlpath := flag.String("ymlpath", "../storage/map.yml", "Path to a YAML file containing shortened URLs") 14 | jsonpath := flag.String("jsonpath", "../storage/map.json", "Path to a JSON file containing shortened URLs") 15 | boltpath := flag.String("boltpath", "../storage/bolt.db", "Path to a BoltDB File containing shortened URLs") 16 | flag.Parse() 17 | 18 | mux := defaultMux() 19 | 20 | // Build the MapHandler using the mux as the fallback 21 | pathsToUrls := map[string]string{ 22 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 23 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 24 | } 25 | urlshort.MapHandler(pathsToUrls, mux) 26 | 27 | if yaml, err := ioutil.ReadFile(*ymlpath); err == nil { 28 | err = urlshort.YAMLHandler([]byte(yaml), mux) 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | if json, err := ioutil.ReadFile(*jsonpath); err == nil { 35 | err = urlshort.JSONHandler([]byte(json), mux) 36 | if err != nil { 37 | panic(err) 38 | } 39 | } 40 | 41 | err := urlshort.BoltHandler(*boltpath, mux) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | fmt.Println("Starting the server on :8080") 47 | http.ListenAndServe(":8080", mux) 48 | } 49 | 50 | func defaultMux() *http.ServeMux { 51 | mux := http.NewServeMux() 52 | mux.HandleFunc("/", hello) 53 | return mux 54 | } 55 | 56 | func hello(w http.ResponseWriter, r *http.Request) { 57 | fmt.Fprintln(w, "Hello, world!") 58 | } 59 | -------------------------------------------------------------------------------- /students/dimdiden/urlshort/bold.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/boltdb/bolt" 8 | ) 9 | 10 | // BoltDB has an embedded bolt.DB instance 11 | // and is used to implement PairProducer 12 | // having access to the database 13 | // https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go 14 | type BDB struct { 15 | *bolt.DB 16 | } 17 | 18 | // OpenDB create a BoltDB instance with default options 19 | func OpenBDB(path string, mode os.FileMode) (*BDB, error) { 20 | db, err := bolt.Open(path, mode, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &BDB{db}, nil 25 | } 26 | 27 | // LoadInitData is used just to create the pairs bucket and to insert one record. 28 | func (bdb *BDB) LoadInitData() error { 29 | if err := bdb.Update(func(tx *bolt.Tx) error { 30 | // create bucket if it doesn't exist 31 | bk, err := tx.CreateBucketIfNotExists([]byte("pairs")) 32 | if err != nil { 33 | return fmt.Errorf("could not create pairs bucket: %v", err) 34 | } 35 | // insert one key-value pair 36 | if err := bk.Put([]byte("/wi"), []byte("https://ru.wikipedia.org")); err != nil { 37 | return fmt.Errorf("could not insert entry: %v", err) 38 | } 39 | return nil 40 | }); err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | // Pair will look for key-value pairs in the "pairs" Bucket 47 | // and will return an array of the Pair structs 48 | func (bdb *BDB) Pair() ([]Pair, error) { 49 | var pairs []Pair 50 | 51 | if err := bdb.View(func(tx *bolt.Tx) error { 52 | b := tx.Bucket([]byte("pairs")) 53 | b.ForEach(func(k, v []byte) error { 54 | pairs = append(pairs, Pair{string(k), string(v)}) 55 | return nil 56 | }) 57 | return nil 58 | }); err != nil { 59 | return nil, err 60 | } 61 | return pairs, nil 62 | } 63 | -------------------------------------------------------------------------------- /students/emrekp/handler_impls.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "gopkg.in/yaml.v2" 5 | "net/http" 6 | ) 7 | 8 | // MapHandler will return an http.HandlerFunc (which also 9 | // implements http.Handler) that will attempt to map any 10 | // paths (keys in the map) to their corresponding URL (values 11 | // that each key in the map points to, in string format). 12 | // If the path is not provided in the map, then the fallback 13 | // http.Handler will be called instead. 14 | func MapHandler(paths map[string]string, fallback http.Handler) http.HandlerFunc { 15 | return func(writer http.ResponseWriter, request *http.Request) { 16 | url := paths[request.URL.Path] 17 | if url != "" { 18 | http.Redirect(writer, request, url, http.StatusFound) 19 | return 20 | } 21 | 22 | fallback.ServeHTTP(writer, request) 23 | } 24 | } 25 | 26 | type YAMLMap struct { 27 | Path string `yaml:"path"` 28 | Url string `yaml:"url"` 29 | } 30 | 31 | // YAMLHandler will parse the provided YAML and then return 32 | // an http.HandlerFunc (which also implements http.Handler) 33 | // that will attempt to map any paths to their corresponding 34 | // URL. If the path is not provided in the YAML, then the 35 | // fallback http.Handler will be called instead. 36 | // 37 | // YAML is expected to be in the format: 38 | // 39 | // - path: /some-path 40 | // url: https://www.some-url.com/demo 41 | // 42 | // The only errors that can be returned all related to having 43 | // invalid YAML data. 44 | // 45 | // See MapHandler to create a similar http.HandlerFunc via 46 | // a mapping of paths to urls. 47 | func YAMLHandler(yml []byte, fallback http.Handler) (http.HandlerFunc, error) { 48 | var ymlMap []YAMLMap 49 | err := yaml.Unmarshal(yml, &ymlMap) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | paths := make(map[string]string) 55 | for _, y := range ymlMap { 56 | paths[y.Path] = y.Url 57 | } 58 | 59 | return MapHandler(paths, fallback), nil 60 | } 61 | -------------------------------------------------------------------------------- /students/kalexmills/urlshort.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "net/http" 5 | "fmt" 6 | 7 | "gopkg.in/yaml.v2" 8 | ) 9 | 10 | // MapHandler will return an http.HandlerFunc (which also 11 | // implements http.Handler) that will attempt to map any 12 | // paths (keys in the map) to their corresponding URL (values 13 | // that each key in the map points to, in string format). 14 | // If the path is not provided in the map, then the fallback 15 | // http.Handler will be called instead. 16 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 17 | return func(out http.ResponseWriter, in *http.Request) { 18 | if in.Method != http.MethodGet { 19 | fallback.ServeHTTP(out, in) 20 | return 21 | } 22 | 23 | url, ok := pathsToUrls[in.URL.Path] 24 | if !ok { 25 | fallback.ServeHTTP(out, in) 26 | return 27 | } 28 | 29 | out.Header().Add("Location", url) 30 | out.WriteHeader(301) 31 | fmt.Printf("%s %s %d: %s\n", in.Method, in.URL.Path, 301, url) 32 | } 33 | } 34 | 35 | // YAMLHandler will parse the provided YAML and then return 36 | // an http.HandlerFunc (which also implements http.Handler) 37 | // that will attempt to map any paths to their corresponding 38 | // URL. If the path is not provided in the YAML, then the 39 | // fallback http.Handler will be called instead. 40 | // 41 | // YAML is expected to be in the format: 42 | // 43 | // pairs: 44 | // - path: /some-path 45 | // url: https://www.some-url.com/demo 46 | func YAMLHandler(yml []byte, fallback http.Handler) (http.HandlerFunc, error) { 47 | type pair struct { 48 | Path string 49 | URL string 50 | } 51 | 52 | type pairs struct { 53 | Pairs []pair 54 | } 55 | 56 | var prs pairs 57 | err := yaml.Unmarshal(yml, &prs) 58 | 59 | pathsToUrls := make(map[string]string, len(prs.Pairs)) 60 | for _, entry := range prs.Pairs { 61 | pathsToUrls[entry.Path] = entry.URL 62 | } 63 | 64 | return MapHandler(pathsToUrls, fallback), err 65 | } -------------------------------------------------------------------------------- /students/latentgenius/handler.go: -------------------------------------------------------------------------------- 1 | package latentgenius 2 | 3 | import ( 4 | "net/http" 5 | 6 | yamlV2 "gopkg.in/yaml.v2" 7 | ) 8 | 9 | // MapHandler will return an http.HandlerFunc (which also 10 | // implements http.Handler) that will attempt to map any 11 | // paths (keys in the map) to their corresponding URL (values 12 | // that each key in the map points to, in string format). 13 | // If the path is not provided in the map, then the fallback 14 | // http.Handler will be called instead. 15 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 16 | return func(w http.ResponseWriter, r *http.Request) { 17 | path, ok := pathsToUrls[r.URL.Path] 18 | if ok { 19 | http.Redirect(w, r, path, http.StatusFound) 20 | } else { 21 | fallback.ServeHTTP(w, r) 22 | } 23 | } 24 | } 25 | 26 | // YAMLHandler will parse the provided YAML and then return 27 | // an http.HandlerFunc (which also implements http.Handler) 28 | // that will attempt to map any paths to their corresponding 29 | // URL. If the path is not provided in the YAML, then the 30 | // fallback http.Handler will be called instead. 31 | // 32 | // YAML is expected to be in the format: 33 | // 34 | // - path: /some-path 35 | // url: https://www.some-url.com/demo 36 | // 37 | // The only errors that can be returned all related to having 38 | // invalid YAML data. 39 | // 40 | // See MapHandler to create a similar http.HandlerFunc via 41 | // a mapping of paths to urls. 42 | func YAMLHandler(yaml []byte, fallback http.Handler) (http.HandlerFunc, error) { 43 | parsedYaml, err := parseYAML(yaml) 44 | if err != nil { 45 | return nil, err 46 | } 47 | pathMap := buildMap(parsedYaml) 48 | return MapHandler(pathMap, fallback), nil 49 | } 50 | 51 | func parseYAML(yaml []byte) (dst []map[string]string, err error) { 52 | err = yamlV2.Unmarshal(yaml, &dst) 53 | return dst, err 54 | } 55 | 56 | func buildMap(parsedYaml []map[string]string) map[string]string { 57 | mergedMap := make(map[string]string) 58 | for _, entry := range parsedYaml { 59 | key := entry["path"] 60 | mergedMap[key] = entry["url"] 61 | } 62 | return mergedMap 63 | } 64 | -------------------------------------------------------------------------------- /students/teimurjan/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | // Config is a struct describing the config parsed from cli arguments 11 | type Config struct { 12 | PathToYAML string 13 | PathToJSON string 14 | } 15 | 16 | func main() { 17 | config := getConfig() 18 | 19 | yamlBytes := getFileBytes(config.PathToYAML) 20 | jsonBytes := getFileBytes(config.PathToJSON) 21 | 22 | mux := makeDefaultMux() 23 | mapHandler := makeMapHandler(mux) 24 | 25 | handler := mapHandler 26 | if yamlBytes != nil { 27 | handler = makeYAMLHandler(yamlBytes, &mapHandler) 28 | } else if jsonBytes != nil { 29 | handler = makeJSONHandler(jsonBytes, &mapHandler) 30 | } 31 | startServer(handler) 32 | } 33 | 34 | func getConfig() *Config { 35 | config := Config{} 36 | flag.StringVar(&config.PathToYAML, "yaml", "", "--yaml=path/to/file.yml") 37 | flag.StringVar(&config.PathToJSON, "json", "", "--json=path/to/file.json") 38 | flag.Parse() 39 | return &config 40 | } 41 | 42 | func getFileBytes(pathToFile string) []byte { 43 | bytes, err := ioutil.ReadFile(pathToFile) 44 | if err != nil { 45 | return nil 46 | } 47 | return bytes 48 | } 49 | 50 | func makeDefaultMux() *http.ServeMux { 51 | mux := http.NewServeMux() 52 | mux.HandleFunc("/", helloWorldHandler) 53 | return mux 54 | } 55 | 56 | func helloWorldHandler(w http.ResponseWriter, r *http.Request) { 57 | fmt.Fprintln(w, "Hello, world!") 58 | } 59 | 60 | func makeMapHandler(mux *http.ServeMux) http.HandlerFunc { 61 | return MapHandler(map[string]string{ 62 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 63 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 64 | }, mux) 65 | } 66 | 67 | func makeYAMLHandler(yamlBytes []byte, fallbackHandler *http.HandlerFunc) http.HandlerFunc { 68 | handler, err := YAMLHandler(yamlBytes, fallbackHandler) 69 | if err != nil { 70 | panic(err) 71 | } 72 | return handler 73 | } 74 | 75 | func makeJSONHandler(jsonBytes []byte, fallbackHandler *http.HandlerFunc) http.HandlerFunc { 76 | handler, err := JSONHandler(jsonBytes, fallbackHandler) 77 | if err != nil { 78 | panic(err) 79 | } 80 | return handler 81 | } 82 | 83 | func startServer(handler http.HandlerFunc) { 84 | fmt.Println("Starting the server on :8080") 85 | http.ListenAndServe(":8080", handler) 86 | } 87 | -------------------------------------------------------------------------------- /students/rnbdev/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/boltdb/bolt" 10 | ) 11 | 12 | func defaultMux() *http.ServeMux { 13 | mux := http.NewServeMux() 14 | mux.HandleFunc("/", hello) 15 | return mux 16 | } 17 | 18 | func hello(w http.ResponseWriter, r *http.Request) { 19 | fmt.Fprintln(w, "Hello, world!") 20 | } 21 | 22 | func main() { 23 | var jsonFile, yamlFile, boltFile string 24 | flag.StringVar(&jsonFile, "json", "", "path to json file.") 25 | flag.StringVar(&yamlFile, "yaml", "", "path to yaml file.") 26 | flag.StringVar(&boltFile, "bolt", "", "path to boltdb file.") 27 | 28 | flag.Parse() 29 | 30 | mux := defaultMux() 31 | 32 | // Build the MapHandler using the mux as the fallback 33 | pathsToUrls := map[string]string{ 34 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 35 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 36 | } 37 | 38 | mapHandler := MapHandler(pathsToUrls, mux) 39 | 40 | if jsonFile != "" { 41 | jsonData, err := ioutil.ReadFile(jsonFile) 42 | if err != nil { 43 | panic(err) 44 | } 45 | // Build the JSONHandler using the mapHandler as the 46 | // fallback 47 | jsonHandler, err := JSONHandler([]byte(jsonData), mapHandler) 48 | if err != nil { 49 | panic(err) 50 | } 51 | fmt.Println("Starting the server on :8080") 52 | http.ListenAndServe(":8080", jsonHandler) 53 | } else if yamlFile != "" { 54 | yamlData, err := ioutil.ReadFile(yamlFile) 55 | if err != nil { 56 | panic(err) 57 | } 58 | // Build the YAMLHandler using the mapHandler as the 59 | // fallback 60 | yamlHandler, err := YAMLHandler([]byte(yamlData), mapHandler) 61 | if err != nil { 62 | panic(err) 63 | } 64 | fmt.Println("Starting the server on :8080") 65 | http.ListenAndServe(":8080", yamlHandler) 66 | } else if boltFile != "" { 67 | db, err := bolt.Open(boltFile, 0600, nil) 68 | if err != nil { 69 | panic(err) 70 | } 71 | defer db.Close() 72 | // Build the BOLTHandler using the mapHandler as the 73 | // fallback 74 | boltHandler := BOLTHandler(db, mapHandler) 75 | fmt.Println("Starting the server on :8080") 76 | http.ListenAndServe(":8080", boltHandler) 77 | } else { 78 | fmt.Println("Starting the server on :8080") 79 | http.ListenAndServe(":8080", mapHandler) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /students/dimdiden/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/dimdiden/gophercises/urlshort/students/dimdiden/urlshort" 12 | ) 13 | 14 | // DEFAULTFILE is the yaml file expected to be loaded by default 15 | const DEFAULTFILE = "map.yaml" 16 | 17 | func main() { 18 | // Flag block 19 | file := flag.String("f", DEFAULTFILE, "specify the path to json or yaml file") 20 | useDB := flag.Bool("d", false, "Enable DB usage. Any provided files will be ignored") 21 | flag.Parse() 22 | 23 | mux := defaultMux() 24 | // Build the MapHandler using the mux as the fallback 25 | pathsToUrls := map[string]string{ 26 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 27 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 28 | } 29 | mapHandler := urlshort.MapHandler(pathsToUrls, mux) 30 | 31 | // pairProducer will be used in the MainHandler 32 | var pairProducer urlshort.PairProducer 33 | // get content from DB or from files 34 | if !*useDB { 35 | // get content from file 36 | var err error 37 | pairProducer, err = getContent(*file) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | } else { 42 | // Open db 43 | db, err := urlshort.OpenBDB("my.db", 0600) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | defer db.Close() 48 | // load test data 49 | if err := db.LoadInitData(); err != nil { 50 | log.Fatal(err) 51 | } 52 | pairProducer = db 53 | } 54 | 55 | // mainHandler will be used as in ListenAndServe 56 | mainHandler, err := urlshort.MainHandler(pairProducer, mapHandler) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | fmt.Println("Starting the server on :8080") 62 | http.ListenAndServe(":8080", mainHandler) 63 | } 64 | 65 | // getContent opens file and returns urlshort.Content 66 | func getContent(file string) (urlshort.Content, error) { 67 | f, err := os.Open(file) 68 | if err != nil { 69 | return nil, err 70 | } 71 | defer f.Close() 72 | content, err := ioutil.ReadAll(f) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return urlshort.Content(content), nil 77 | } 78 | 79 | func defaultMux() *http.ServeMux { 80 | mux := http.NewServeMux() 81 | mux.HandleFunc("/", hello) 82 | return mux 83 | } 84 | 85 | func hello(w http.ResponseWriter, r *http.Request) { 86 | fmt.Fprintln(w, "Hello, world!") 87 | } 88 | -------------------------------------------------------------------------------- /students/teimurjan/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | yaml "gopkg.in/yaml.v2" 8 | ) 9 | 10 | // MapHandler will return an http.HandlerFunc (which also 11 | // implements http.Handler) that will attempt to map any 12 | // paths (keys in the map) to their corresponding URL (values 13 | // that each key in the map points to, in string format). 14 | // If the path is not provided in the map, then the fallback 15 | // http.Handler will be called instead. 16 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 17 | return func(w http.ResponseWriter, r *http.Request) { 18 | originalURL, ok := pathsToUrls[r.URL.Path] 19 | if ok { 20 | http.Redirect(w, r, originalURL, 301) 21 | } else { 22 | fallback.ServeHTTP(w, r) 23 | } 24 | } 25 | } 26 | 27 | // YAMLHandler will parse the provided YAML and then return 28 | // an http.HandlerFunc (which also implements http.Handler) 29 | // that will attempt to map any paths to their corresponding 30 | // URL. If the path is not provided in the YAML, then the 31 | // fallback http.Handler will be called instead. 32 | // 33 | // YAML is expected to be in the format: 34 | // 35 | // - path: /some-path 36 | // url: https://www.some-url.com/demo 37 | // 38 | // The only errors that can be returned all related to having 39 | // invalid YAML data. 40 | // 41 | // See MapHandler to create a similar http.HandlerFunc via 42 | // a mapping of paths to urls. 43 | 44 | type URLMapper struct { 45 | Path string `yaml:"path" json:"path"` 46 | URL string `yaml:"url" json:"url"` 47 | } 48 | 49 | func YAMLHandler(YAML []byte, fallback http.Handler) (http.HandlerFunc, error) { 50 | var mappers []URLMapper 51 | err := yaml.Unmarshal(YAML, &mappers) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return func(w http.ResponseWriter, r *http.Request) { 56 | for _, mapper := range mappers { 57 | if mapper.Path == r.URL.Path { 58 | http.Redirect(w, r, mapper.URL, 301) 59 | return 60 | } 61 | } 62 | fallback.ServeHTTP(w, r) 63 | }, nil 64 | } 65 | 66 | func JSONHandler(JSON []byte, fallback http.Handler) (http.HandlerFunc, error) { 67 | var mappers []URLMapper 68 | err := json.Unmarshal(JSON, &mappers) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return func(w http.ResponseWriter, r *http.Request) { 73 | for _, mapper := range mappers { 74 | if mapper.Path == r.URL.Path { 75 | http.Redirect(w, r, mapper.URL, 301) 76 | return 77 | } 78 | } 79 | fallback.ServeHTTP(w, r) 80 | }, nil 81 | } 82 | -------------------------------------------------------------------------------- /students/dennisvis/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/DennisVis/urlshort/students/dennisvis/urlshort" 13 | "github.com/boltdb/bolt" 14 | ) 15 | 16 | var ( 17 | pathsFile = flag.String("pathsFile", "paths.yml", "The file containing shortened paths to URL's") 18 | initDB = flag.Bool("initDB", true, "Whether or not to initialize the paths database") 19 | ) 20 | 21 | func getFileBytes(fileName string) []byte { 22 | f, err := os.Open(fileName) 23 | if err != nil { 24 | log.Fatalf("Could not open file %s", fileName) 25 | } 26 | 27 | buf := new(bytes.Buffer) 28 | _, err = buf.ReadFrom(f) 29 | if err != nil { 30 | log.Fatalf("Could not read file %s", fileName) 31 | } 32 | 33 | return buf.Bytes() 34 | } 35 | 36 | func initBoltDB(db *bolt.DB) error { 37 | return db.Update(func(tx *bolt.Tx) error { 38 | b, err := tx.CreateBucketIfNotExists([]byte("paths")) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | err = b.Put([]byte("/urlshort"), []byte("https://github.com/gophercises/urlshort")) 44 | err = b.Put([]byte("/urlshort-final"), []byte("https://github.com/gophercises/urlshort/tree/solution")) 45 | 46 | return err 47 | }) 48 | } 49 | 50 | func main() { 51 | mux := defaultMux() 52 | 53 | flag.Parse() 54 | 55 | ext := filepath.Ext(*pathsFile) 56 | 57 | var handler http.Handler 58 | var err error 59 | if ext == ".yml" { 60 | handler, err = urlshort.YAMLHandler(getFileBytes(*pathsFile), mux) 61 | if err != nil { 62 | panic(err) 63 | } 64 | } else if ext == ".json" { 65 | handler, err = urlshort.JSONHandler(getFileBytes(*pathsFile), mux) 66 | if err != nil { 67 | panic(err) 68 | } 69 | } else if ext == ".db" { 70 | db, err := bolt.Open(*pathsFile, 0600, nil) 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | defer db.Close() 76 | 77 | if *initDB { 78 | if err = initBoltDB(db); err != nil { 79 | panic(err) 80 | } 81 | } 82 | 83 | handler = urlshort.DBHandler(db, mux) 84 | } else { 85 | log.Fatal("Paths file needs to be either a YAML, a JSON or a bolt DB file") 86 | } 87 | 88 | fmt.Println("Starting the server on :8080") 89 | http.ListenAndServe(":8080", handler) 90 | } 91 | 92 | func defaultMux() *http.ServeMux { 93 | mux := http.NewServeMux() 94 | mux.HandleFunc("/", hello) 95 | return mux 96 | } 97 | 98 | func hello(w http.ResponseWriter, r *http.Request) { 99 | fmt.Fprintln(w, "Hello, world!") 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exercise #2: URL Shortener 2 | 3 | [![exercise status: released](https://img.shields.io/badge/exercise%20status-released-green.svg?style=for-the-badge)](https://gophercises.com/exercises/urlshort) 4 | 5 | 6 | 7 | ## Exercise details 8 | 9 | The goal of this exercise is to create an [http.Handler](https://golang.org/pkg/net/http/#Handler) that will look at the path of any incoming web request and determine if it should redirect the user to a new page, much like URL shortener would. 10 | 11 | For instance, if we have a redirect setup for `/dogs` to `https://www.somesite.com/a-story-about-dogs` we would look for any incoming web requests with the path `/dogs` and redirect them. 12 | 13 | To complete this exercises you will need to implement the stubbed out methods in [handler.go](https://github.com/gophercises/urlshort/blob/master/handler.go). There are a good bit of comments explaining what each method should do, and there is also a [main/main.go](https://github.com/gophercises/urlshort/blob/master/main/main.go) source file that uses the package to help you test your code and get an idea of what your program should be doing. 14 | 15 | I suggest first commenting out all of the code in main.go related to the `YAMLHandler` function and focusing on implementing the `MapHandler` function first. 16 | 17 | Once you have that working, focus on parsing the YAML using the [gopkg.in/yaml.v2](https://godoc.org/gopkg.in/yaml.v2) package. *Note: You will need to `go get` this package if you don't have it already.* 18 | 19 | After you get the YAML parsing down, try to convert the data into a map and then use the MapHandler to finish the YAMLHandler implementation. Eg you might end up with some code like this: 20 | 21 | ```go 22 | func YAMLHandler(yaml []byte, fallback http.Handler) (http.HandlerFunc, error) { 23 | parsedYaml, err := parseYAML(yaml) 24 | if err != nil { 25 | return nil, err 26 | } 27 | pathMap := buildMap(parsedYaml) 28 | return MapHandler(pathMap, fallback), nil 29 | } 30 | ``` 31 | 32 | But in order for this to work you will need to create functions like `parseYAML` and `buildMap` on your own. This should give you ample experience working with YAML data. 33 | 34 | 35 | ## Bonus 36 | 37 | As a bonus exercises you can also... 38 | 39 | 1. Update the [main/main.go](https://github.com/gophercises/urlshort/blob/master/main/main.go) source file to accept a YAML file as a flag and then load the YAML from a file rather than from a string. 40 | 2. Build a JSONHandler that serves the same purpose, but reads from JSON data. 41 | 3. Build a Handler that doesn't read from a map but instead reads from a database. Whether you use BoltDB, SQL, or something else is entirely up to you. 42 | -------------------------------------------------------------------------------- /students/hackeryarn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gophercises/urlshort/students/hackeryarn/urlshort" 10 | ) 11 | 12 | const ( 13 | // YAMLFlag is used to set urls for yaml 14 | YAMLFlag = "yaml" 15 | // YAMLFlagValue is the value used when no YAMLFlag is provided 16 | YAMLFlagValue = "urls.yaml" 17 | // YAMLFlagUsage is the help string for the YAMLFlag 18 | YAMLFlagUsage = "URLs file in yaml format" 19 | 20 | // JSONFlag is used to set a file for the questions 21 | JSONFlag = "json" 22 | // JSONFlagValue is the value used when no JSONFlag is provided 23 | JSONFlagValue = "urls.json" 24 | // JSONFlagUsage is the help string for the JSONFlag 25 | JSONFlagUsage = "URLs file in json format" 26 | ) 27 | 28 | // Flagger is an interface for configuring various flags 29 | type Flagger interface { 30 | StringVar(p *string, name, value, usage string) 31 | } 32 | 33 | type urlshortFlagger struct{} 34 | 35 | func (uf *urlshortFlagger) StringVar(p *string, name, value, usage string) { 36 | flag.StringVar(p, name, value, usage) 37 | } 38 | 39 | var yaml string 40 | var json string 41 | 42 | // ConfigFlags will configure the flags used by the application 43 | func ConfigFlags(flagger Flagger) { 44 | flagger.StringVar(&yaml, YAMLFlag, YAMLFlagValue, YAMLFlagUsage) 45 | flagger.StringVar(&json, JSONFlag, JSONFlagValue, JSONFlagUsage) 46 | } 47 | 48 | func main() { 49 | flagger := &urlshortFlagger{} 50 | ConfigFlags(flagger) 51 | 52 | mapHandler := createMapHandler() 53 | yamlHandler := createYAMLHandler(mapHandler) 54 | jsonHandler := createJSONHandler(yamlHandler) 55 | 56 | fmt.Println("Starting the server on :8080") 57 | http.ListenAndServe(":8080", jsonHandler) 58 | } 59 | 60 | // Build the MapHandler using the mux as the fallback 61 | var pathsToUrls = map[string]string{ 62 | "/urlshort-godoc": "https://godoc.org/github.com/gophercises/urlshort", 63 | "/yaml-godoc": "https://godoc.org/gopkg.in/yaml.v2", 64 | } 65 | 66 | func createMapHandler() http.HandlerFunc { 67 | mux := defaultMux() 68 | return urlshort.MapHandler(pathsToUrls, mux) 69 | } 70 | 71 | func defaultMux() *http.ServeMux { 72 | mux := http.NewServeMux() 73 | mux.HandleFunc("/", hello) 74 | return mux 75 | } 76 | 77 | func hello(w http.ResponseWriter, r *http.Request) { 78 | fmt.Fprintln(w, "Hello, world!") 79 | } 80 | 81 | func createYAMLHandler(fallback http.HandlerFunc) http.HandlerFunc { 82 | yamlFile, err := os.Open(yaml) 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | yamlHandler, err := urlshort.YAMLHandler(yamlFile, fallback) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | return yamlHandler 93 | } 94 | 95 | func createJSONHandler(fallback http.HandlerFunc) http.HandlerFunc { 96 | jsonFile, err := os.Open(json) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | jsonHandler, err := urlshort.JSONHandler(jsonFile, fallback) 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | return jsonHandler 107 | } 108 | -------------------------------------------------------------------------------- /students/hackeryarn/urlshort/handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | 8 | yaml "gopkg.in/yaml.v2" 9 | ) 10 | 11 | // MapHandler will return an http.HandlerFunc (which also 12 | // implements http.Handler) that will attempt to map any 13 | // paths (keys in the map) to their corresponding URL (values 14 | // that each key in the map points to, in string format). 15 | // If the path is not provided in the map, then the fallback 16 | // http.Handler will be called instead. 17 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 18 | return func(w http.ResponseWriter, r *http.Request) { 19 | path := r.URL.Path 20 | if dest, ok := pathsToUrls[path]; ok { 21 | http.Redirect(w, r, dest, http.StatusFound) 22 | } 23 | 24 | fallback.ServeHTTP(w, r) 25 | } 26 | } 27 | 28 | // YAMLHandler will parse the provided YAML and then return 29 | // an http.HandlerFunc (which also implements http.Handler) 30 | // that will attempt to map any paths to their corresponding 31 | // URL. If the path is not provided in the YAML, then the 32 | // fallback http.Handler will be called instead. 33 | // 34 | // YAML is expected to be in the format: 35 | // 36 | // - path: /some-path 37 | // url: https://www.some-url.com/demo 38 | // 39 | // The only errors that can be returned all related to having 40 | // invalid YAML data. 41 | // 42 | // See MapHandler to create a similar http.HandlerFunc via 43 | // a mapping of paths to urls. 44 | func YAMLHandler(r io.Reader, fallback http.Handler) (http.HandlerFunc, error) { 45 | decoder := yaml.NewDecoder(r) 46 | pathURLs, err := decode(decoder) 47 | if err != nil { 48 | return nil, err 49 | } 50 | pathToUrls := buildMap(pathURLs) 51 | 52 | mapHandler := MapHandler(pathToUrls, fallback) 53 | return mapHandler, nil 54 | } 55 | 56 | // JSONHandler will parse the provided JSON and then return 57 | // an http.HandlerFunc that will attempt to map any paths to their 58 | // corresponding URL. If the path is not provided in the JSON, then the 59 | // fallback http.Handler will be called instead. 60 | // 61 | // JSON is expected to be in the format: 62 | // [ 63 | // { 64 | // "path": "/some-path", 65 | // "url": "https://www.some-url.com/demo" 66 | // } 67 | // ] 68 | func JSONHandler(r io.Reader, fallback http.Handler) (http.HandlerFunc, error) { 69 | decoder := json.NewDecoder(r) 70 | pathURLs, err := decode(decoder) 71 | if err != nil { 72 | return nil, err 73 | } 74 | pathToUrls := buildMap(pathURLs) 75 | 76 | mapHandler := MapHandler(pathToUrls, fallback) 77 | return mapHandler, nil 78 | } 79 | 80 | type pathURL struct { 81 | Path string `yaml:"path" json:"path"` 82 | URL string `yaml:"url" json:"url"` 83 | } 84 | 85 | type decoder interface { 86 | Decode(v interface{}) error 87 | } 88 | 89 | func decode(d decoder) ([]pathURL, error) { 90 | var pu []pathURL 91 | for { 92 | err := d.Decode(&pu) 93 | if err == io.EOF { 94 | return pu, nil 95 | } else if err != nil { 96 | return nil, err 97 | } 98 | } 99 | } 100 | 101 | func buildMap(pathURLs []pathURL) map[string]string { 102 | pathToUrls := make(map[string]string) 103 | for _, pu := range pathURLs { 104 | pathToUrls[pu.Path] = pu.URL 105 | } 106 | 107 | return pathToUrls 108 | } 109 | -------------------------------------------------------------------------------- /students/liikt/handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/boltdb/bolt" 8 | 9 | yaml "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type urlMap struct { 13 | paths map[string]string 14 | } 15 | 16 | type mapItem struct { 17 | Path string 18 | Url string 19 | } 20 | 21 | var globalMap *urlMap = &urlMap{make(map[string]string)} 22 | 23 | func (*urlMap) redirect(w http.ResponseWriter, r *http.Request) { 24 | if url, ok := globalMap.paths[r.URL.String()]; ok { 25 | http.Redirect(w, r, url, 307) 26 | return 27 | } 28 | } 29 | 30 | // MapHandler will return an http.HandlerFunc (which also 31 | // implements http.Handler) that will attempt to map any 32 | // paths (keys in the map) to their corresponding URL (values 33 | // that each key in the map points to, in string format). 34 | // If the path is not provided in the map, then the fallback 35 | // http.Handler will be called instead. 36 | func MapHandler(pathsToUrls map[string]string, fallback *http.ServeMux) { 37 | for k, v := range pathsToUrls { 38 | globalMap.paths[k] = v 39 | fallback.HandleFunc(k, globalMap.redirect) 40 | } 41 | } 42 | 43 | // YAMLHandler will parse the provided YAML and then return 44 | // an http.HandlerFunc (which also implements http.Handler) 45 | // that will attempt to map any paths to their corresponding 46 | // URL. If the path is not provided in the YAML, then the 47 | // fallback http.Handler will be called instead. 48 | // 49 | // YAML is expected to be in the format: 50 | // 51 | // - path: /some-path 52 | // url: https://www.some-url.com/demo 53 | // 54 | // The only errors that can be returned all related to having 55 | // invalid YAML data. 56 | // 57 | // See MapHandler to create a similar http.HandlerFunc via 58 | // a mapping of paths to urls. 59 | func YAMLHandler(yml []byte, fallback *http.ServeMux) error { 60 | var list []mapItem 61 | err := yaml.Unmarshal(yml, &list) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | for _, item := range list { 67 | globalMap.paths[item.Path] = item.Url 68 | fallback.HandleFunc(item.Path, globalMap.redirect) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func JSONHandler(jsn []byte, fallback *http.ServeMux) error { 75 | var list []mapItem 76 | err := json.Unmarshal(jsn, &list) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | for _, item := range list { 82 | globalMap.paths[item.Path] = item.Url 83 | fallback.HandleFunc(item.Path, globalMap.redirect) 84 | } 85 | 86 | return nil 87 | } 88 | 89 | func BoltHandler(path string, fallback *http.ServeMux) error { 90 | db, err := bolt.Open(path, 0666, nil) 91 | if err != nil { 92 | return nil 93 | } 94 | defer db.Close() 95 | 96 | // Insert testdata into a bucket. 97 | if err := db.Update(func(tx *bolt.Tx) error { 98 | b, err := tx.CreateBucketIfNotExists([]byte("paths")) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | if err := b.Put([]byte("/git"), []byte("https://github.com/")); err != nil { 104 | return err 105 | } 106 | if err := b.Put([]byte("/radare"), []byte("http://radare.today/posts/using-radare2/")); err != nil { 107 | return err 108 | } 109 | return nil 110 | }); err != nil { 111 | return err 112 | } 113 | 114 | if err := db.View(func(tx *bolt.Tx) error { 115 | b := tx.Bucket([]byte("paths")) 116 | 117 | c := b.Cursor() 118 | 119 | for k, v := c.First(); k != nil; k, v = c.Next() { 120 | globalMap.paths[string(k)] = string(v) 121 | fallback.HandleFunc(string(k), globalMap.redirect) 122 | } 123 | 124 | return nil 125 | }); err != nil { 126 | return err 127 | } 128 | 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /students/dennisvis/urlshort/handler.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/boltdb/bolt" 8 | "gopkg.in/yaml.v2" 9 | ) 10 | 11 | type pathToURL struct { 12 | Path string 13 | URL string 14 | } 15 | 16 | // MapHandler will return an http.HandlerFunc (which also 17 | // implements http.Handler) that will attempt to map any 18 | // paths (keys in the map) to their corresponding URL (values 19 | // that each key in the map points to, in string format). 20 | // If the path is not provided in the map, then the fallback 21 | // http.Handler will be called instead. 22 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 23 | return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 24 | url := pathsToUrls[req.URL.Path] 25 | if url != "" { 26 | http.Redirect(res, req, url, http.StatusPermanentRedirect) 27 | } else { 28 | fallback.ServeHTTP(res, req) 29 | } 30 | }) 31 | } 32 | 33 | func buildMap(pathsToURLs []pathToURL) (builtMap map[string]string) { 34 | builtMap = make(map[string]string) 35 | for _, ptu := range pathsToURLs { 36 | builtMap[ptu.Path] = ptu.URL 37 | } 38 | return 39 | } 40 | 41 | func parseYAML(yamlData []byte) (pathsToURLs []pathToURL, err error) { 42 | err = yaml.Unmarshal(yamlData, &pathsToURLs) 43 | return 44 | } 45 | 46 | // YAMLHandler will parse the provided YAML and then return 47 | // an http.HandlerFunc (which also implements http.Handler) 48 | // that will attempt to map any paths to their corresponding 49 | // URL. If the path is not provided in the YAML, then the 50 | // fallback http.Handler will be called instead. 51 | // 52 | // YAML is expected to be in the format: 53 | // 54 | // - path: /some-path 55 | // url: https://www.some-url.com/demo 56 | // 57 | // The only errors that can be returned all related to having 58 | // invalid YAML data. 59 | // 60 | // See MapHandler to create a similar http.HandlerFunc via 61 | // a mapping of paths to urls. 62 | func YAMLHandler(yamlData []byte, fallback http.Handler) (yamlHandler http.HandlerFunc, err error) { 63 | parsedYaml, err := parseYAML(yamlData) 64 | if err != nil { 65 | return 66 | } 67 | pathMap := buildMap(parsedYaml) 68 | yamlHandler = MapHandler(pathMap, fallback) 69 | return 70 | } 71 | 72 | func parseJSON(jsonData []byte) (pathsToURLs []pathToURL, err error) { 73 | err = json.Unmarshal(jsonData, &pathsToURLs) 74 | return 75 | } 76 | 77 | // JSONHandler will parse the provided JSON and then return 78 | // an http.HandlerFunc (which also implements http.Handler) 79 | // that will attempt to map any paths to their corresponding 80 | // URL. If the path is not provided in the JSON, then the 81 | // fallback http.Handler will be called instead. 82 | // 83 | // JSON is expected to be in the format: 84 | // 85 | // [ 86 | // { 87 | // "path": "/some-path", 88 | // "url": "https://www.some-url.com/demo" 89 | // } 90 | // ] 91 | // 92 | // The only errors that can be returned all related to having 93 | // invalid JSON data. 94 | func JSONHandler(jsonData []byte, fallback http.Handler) (jsonHandler http.HandlerFunc, err error) { 95 | parsedJSON, err := parseJSON(jsonData) 96 | if err != nil { 97 | return 98 | } 99 | pathMap := buildMap(parsedJSON) 100 | jsonHandler = MapHandler(pathMap, fallback) 101 | return 102 | } 103 | 104 | // DBHandler will use the provided Bolt database and then return 105 | // an http.HandlerFunc (which also implements http.Handler) 106 | // that will attempt to map any paths to their corresponding 107 | // URL. If the path is not provided in the DB, then the 108 | // fallback http.Handler will be called instead. 109 | func DBHandler(db *bolt.DB, fallback http.Handler) http.HandlerFunc { 110 | return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 111 | var url string 112 | err := db.View(func(tx *bolt.Tx) error { 113 | b := tx.Bucket([]byte("paths")) 114 | bts := b.Get([]byte(req.URL.Path)) 115 | if bts != nil { 116 | url = string(bts) 117 | } 118 | return nil 119 | }) 120 | 121 | if err == nil && url != "" { 122 | http.Redirect(res, req, url, http.StatusTemporaryRedirect) 123 | } else { 124 | fallback.ServeHTTP(res, req) 125 | } 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /students/hackeryarn/urlshort/handler_test.go: -------------------------------------------------------------------------------- 1 | package urlshort 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | var ( 14 | fallbackResponse = "fallback" 15 | path = "/test" 16 | dest = "https://test.com" 17 | ) 18 | 19 | func fallback(w http.ResponseWriter, r *http.Request) { 20 | fmt.Fprint(w, fallbackResponse) 21 | } 22 | 23 | func TestMapHandler(t *testing.T) { 24 | pathToUrls := map[string]string{path: dest} 25 | 26 | t.Run("it uses the fallback for unknown routes", func(t *testing.T) { 27 | result := runMapHandler(pathToUrls, "/unknown") 28 | 29 | assertBody(t, result, fallbackResponse) 30 | }) 31 | 32 | t.Run("it redirects for found url", func(t *testing.T) { 33 | result := runMapHandler(pathToUrls, path) 34 | 35 | assertStatus(t, result, http.StatusFound) 36 | assertURL(t, result, dest) 37 | }) 38 | } 39 | 40 | func TestYAMLHandler(t *testing.T) { 41 | yaml := fmt.Sprintf(` 42 | - path: %s 43 | url: %s 44 | `, path, dest) 45 | 46 | t.Run("it uses the fallback for unknown routes", func(t *testing.T) { 47 | result := runReaderHandler(YAMLHandler, yaml, "/unknown") 48 | 49 | assertBody(t, result, fallbackResponse) 50 | }) 51 | 52 | t.Run("it redirects for found url", func(t *testing.T) { 53 | result := runReaderHandler(YAMLHandler, yaml, path) 54 | 55 | assertStatus(t, result, http.StatusFound) 56 | assertURL(t, result, dest) 57 | }) 58 | } 59 | 60 | func TestJSONHandler(t *testing.T) { 61 | json := fmt.Sprintf(`[ 62 | { 63 | "path": "%s", 64 | "url": "%s" 65 | } 66 | ]`, path, dest) 67 | 68 | t.Run("it uses the fallback for unknown routes", func(t *testing.T) { 69 | result := runReaderHandler(JSONHandler, json, "/unknown") 70 | 71 | assertBody(t, result, fallbackResponse) 72 | }) 73 | 74 | t.Run("it redirects for found url", func(t *testing.T) { 75 | result := runReaderHandler(JSONHandler, json, path) 76 | 77 | assertStatus(t, result, http.StatusFound) 78 | assertURL(t, result, dest) 79 | }) 80 | } 81 | 82 | func runMapHandler(pathToUrls map[string]string, path string) *http.Response { 83 | request, _ := http.NewRequest(http.MethodGet, path, nil) 84 | response := httptest.NewRecorder() 85 | 86 | mapHandler := createMapHandler(pathToUrls) 87 | mapHandler(response, request) 88 | 89 | return response.Result() 90 | } 91 | 92 | func createMapHandler(pathToUrls map[string]string) http.HandlerFunc { 93 | fallbackHandler := http.HandlerFunc(fallback) 94 | return MapHandler(pathToUrls, fallbackHandler) 95 | } 96 | 97 | type handlerFunc func(io.Reader, http.Handler) (http.HandlerFunc, error) 98 | 99 | func runReaderHandler(handler handlerFunc, body, path string) *http.Response { 100 | request, _ := http.NewRequest(http.MethodGet, path, nil) 101 | response := httptest.NewRecorder() 102 | 103 | handlerF := createReaderHandler(handler, body) 104 | handlerF(response, request) 105 | 106 | return response.Result() 107 | } 108 | 109 | func createReaderHandler(handler handlerFunc, yaml string) http.HandlerFunc { 110 | yamlReader := bytes.NewBufferString(yaml) 111 | fallbackHandler := http.HandlerFunc(fallback) 112 | 113 | handlerF, err := handler(yamlReader, fallbackHandler) 114 | if err != nil { 115 | fmt.Println(err) 116 | panic("could not create a Reader handler") 117 | } 118 | 119 | return handlerF 120 | } 121 | 122 | func assertStatus(t *testing.T, resp *http.Response, want int) { 123 | t.Helper() 124 | if resp.StatusCode != want { 125 | t.Errorf("Expected status to be %d, got %d", 126 | want, resp.StatusCode) 127 | } 128 | } 129 | 130 | func assertBody(t *testing.T, resp *http.Response, want string) { 131 | t.Helper() 132 | body, err := ioutil.ReadAll(resp.Body) 133 | 134 | if err != nil { 135 | t.Fatal("Could not ready response body", err) 136 | } 137 | 138 | got := string(body) 139 | if want != got { 140 | t.Errorf("Expected response body to be %s, got %s", 141 | want, got) 142 | } 143 | } 144 | 145 | func assertURL(t *testing.T, resp *http.Response, want string) { 146 | t.Helper() 147 | url, err := resp.Location() 148 | 149 | if err != nil { 150 | t.Fatal("Could not read location", err) 151 | } 152 | 153 | if url.String() != want { 154 | t.Errorf("Expected url to be %s, got %s", url, want) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /students/rnbdev/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "encoding/json" 7 | 8 | "github.com/boltdb/bolt" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // MapHandler will return an http.HandlerFunc (which also 13 | // implements http.Handler) that will attempt to map any 14 | // paths (keys in the map) to their corresponding URL (values 15 | // that each key in the map points to, in string format). 16 | // If the path is not provided in the map, then the fallback 17 | // http.Handler will be called instead. 18 | func MapHandler(pathsToUrls map[string]string, fallback http.Handler) http.HandlerFunc { 19 | return func(w http.ResponseWriter, r *http.Request) { 20 | if path, ok := pathsToUrls[r.URL.Path]; ok { 21 | http.Redirect(w, r, path, http.StatusFound) 22 | } 23 | fallback.ServeHTTP(w, r) 24 | } 25 | } 26 | 27 | // YAMLHandler will parse the provided YAML and then return 28 | // an http.HandlerFunc (which also implements http.Handler) 29 | // that will attempt to map any paths to their corresponding 30 | // URL. If the path is not provided in the YAML, then the 31 | // fallback http.Handler will be called instead. 32 | // 33 | // YAML is expected to be in the format: 34 | // 35 | // - path: /some-path 36 | // url: https://www.some-url.com/demo 37 | // 38 | // The only errors that can be returned all related to having 39 | // invalid YAML data. 40 | // 41 | // See MapHandler to create a similar http.HandlerFunc via 42 | // a mapping of paths to urls. 43 | func YAMLHandler(yamldata []byte, fallback http.Handler) (http.HandlerFunc, error) { 44 | var pathsToUrls []struct { 45 | Path string `yaml:"path"` 46 | URL string `yaml:"url"` 47 | } 48 | if err := yaml.Unmarshal(yamldata, &pathsToUrls); err != nil { 49 | return nil, err 50 | } 51 | return func(w http.ResponseWriter, r *http.Request) { 52 | for _, pathtourl := range pathsToUrls { 53 | if pathtourl.Path == r.URL.Path { 54 | http.Redirect(w, r, pathtourl.URL, http.StatusFound) 55 | return 56 | } 57 | } 58 | fallback.ServeHTTP(w, r) 59 | }, nil 60 | } 61 | 62 | // JSONHandler will parse the provided JSON and then return 63 | // an http.HandlerFunc (which also implements http.Handler) 64 | // that will attempt to map any paths to their corresponding 65 | // URL. If the path is not provided in the JSON, then the 66 | // fallback http.Handler will be called instead. 67 | // 68 | // JSON is expected to be in the format: 69 | // 70 | // { 71 | // "path": "/some-path", 72 | // "url": "https://www.some-url.com/demo" 73 | // } 74 | // 75 | // The only errors that can be returned all related to having 76 | // invalid JSON data. 77 | // 78 | // See MapHandler to create a similar http.HandlerFunc via 79 | // a mapping of paths to urls. 80 | func JSONHandler(jsondata []byte, fallback http.Handler) (http.HandlerFunc, error) { 81 | var pathsToUrls []struct { 82 | Path string `json:"path"` 83 | URL string `json:"url"` 84 | } 85 | if err := json.Unmarshal(jsondata, &pathsToUrls); err != nil { 86 | return nil, err 87 | } 88 | return func(w http.ResponseWriter, r *http.Request) { 89 | for _, pathtourl := range pathsToUrls { 90 | if pathtourl.Path == r.URL.Path { 91 | http.Redirect(w, r, pathtourl.URL, http.StatusFound) 92 | return 93 | } 94 | } 95 | fallback.ServeHTTP(w, r) 96 | }, nil 97 | } 98 | 99 | // BOLTHandler will use the provided BoltDB and then return 100 | // an http.HandlerFunc (which also implements http.Handler) 101 | // that will attempt to map any paths to their corresponding 102 | // URL. If the path is not provided in the BoltDB, then the 103 | // fallback http.Handler will be called instead. 104 | // 105 | // BoltDB is expected to be in the format: 106 | // 107 | // Bucket(pathstourls) 108 | // /some-path -> https://www.some-url.com/demo 109 | // 110 | // See MapHandler to create a similar http.HandlerFunc via 111 | // a mapping of paths to urls. 112 | func BOLTHandler(db *bolt.DB, fallback http.Handler) http.HandlerFunc { 113 | return func(w http.ResponseWriter, r *http.Request) { 114 | if err := db.View(func(tx *bolt.Tx) error { 115 | bucket := tx.Bucket([]byte("pathstourls")) 116 | if bucket != nil { 117 | cursor := bucket.Cursor() 118 | for path, url := cursor.First(); path != nil; path, url = cursor.Next() { 119 | if string(path) == r.URL.Path { 120 | http.Redirect(w, r, string(url), http.StatusFound) 121 | return nil 122 | } 123 | } 124 | } 125 | return nil 126 | }); err != nil { 127 | panic(err) 128 | } 129 | fallback.ServeHTTP(w, r) 130 | } 131 | } 132 | --------------------------------------------------------------------------------