diff --git a/web/bindata.go b/web/bindata.go new file mode 100644 index 0000000..908c5d9 --- /dev/null +++ b/web/bindata.go @@ -0,0 +1,331 @@ +// Code generated by go-bindata. +// sources: +// static/main.css +// templates/_foot.html +// templates/_head.html +// templates/index.html +// templates/redirect.html +// DO NOT EDIT! + +package web + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _staticMainCss = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x4a\xca\x4f\xa9\x54\xa8\xe6\x52\x50\x48\xcb\xcf\x2b\xd1\x4d\x4b\xcc\xcd\xcc\xa9\xb4\x52\x50\xf2\x48\xcd\x29\x4b\x2d\xc9\x4c\x4e\x54\xf0\x4b\x2d\x4d\x55\xd2\x51\x80\x0b\xe8\x28\x28\x05\xa7\xa6\xe7\xa7\x2a\x84\x7a\x02\x85\x1d\x8b\x32\x13\x73\x74\x14\xd2\x8a\x52\x53\x8b\x13\xf3\x8a\x75\x14\x40\xa4\x6e\x71\x6a\x51\x66\x1a\x50\xa1\x63\x41\x41\x4e\xaa\x82\x73\x7e\x4e\x7e\x91\x82\x6b\x6e\x7e\x56\xa6\x12\x92\x6e\x2c\x22\xc1\x95\xb9\x49\xf9\x39\x4a\xd6\x5c\xb5\x5c\x80\x00\x00\x00\xff\xff\x3e\xe1\x83\x30\x98\x00\x00\x00") + +func staticMainCssBytes() ([]byte, error) { + return bindataRead( + _staticMainCss, + "static/main.css", + ) +} + +func staticMainCss() (*asset, error) { + bytes, err := staticMainCssBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "static/main.css", size: 152, mode: os.FileMode(420), modTime: time.Unix(1468518898, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templates_footHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x52\x50\xb0\xd1\x4f\xca\x4f\xa9\xb4\xe3\xb2\xd1\xcf\x28\xc9\xcd\xb1\xe3\x02\x04\x00\x00\xff\xff\xd2\x42\x65\xbd\x12\x00\x00\x00") + +func templates_footHtmlBytes() ([]byte, error) { + return bindataRead( + _templates_footHtml, + "templates/_foot.html", + ) +} + +func templates_footHtml() (*asset, error) { + bytes, err := templates_footHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/_foot.html", size: 18, mode: os.FileMode(420), modTime: time.Unix(1468764419, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templates_headHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x90\xc1\x6a\xf3\x30\x10\x84\xef\x79\x8a\xfd\x75\xfe\x15\xd3\x5b\x0f\x56\x20\x24\x39\x14\x0a\x2d\x6d\x03\xed\x51\x91\x27\xd5\x12\x59\x72\xa5\x75\x42\xfa\xf4\x75\x62\x02\x2e\x3d\x69\x06\xe9\xd3\xcc\x6e\xfd\x6f\xfd\xb4\x7a\xfb\x78\xde\x90\x97\x36\x2c\x66\xf5\x78\x10\xd5\x1e\xb6\xb9\x88\x41\xb6\x10\x4b\xce\xdb\x5c\x20\x46\xf5\xb2\xd7\xf7\x6a\x7a\xe5\x45\x3a\x8d\xaf\x9e\x8f\x46\xbd\xeb\xed\x52\xaf\x52\xdb\x59\xe1\x5d\x80\x22\x97\xa2\x20\x0e\xdc\xc3\xc6\xa0\xf9\xc4\x2f\x32\xda\x16\x46\x1d\x19\xa7\x2e\x65\x99\x3c\x3e\x71\x23\xde\x34\x38\xb2\x83\xbe\x9a\xff\xc4\x91\x85\x6d\xd0\xc5\xd9\x00\x73\x77\xfb\x48\x58\x02\x16\xe9\xbb\x9f\x73\xaa\xab\xd1\xfd\x89\x68\x50\x5c\xe6\x4e\x38\xc5\x49\xca\x08\x91\xa6\x25\xad\x73\x1f\x0f\x88\xb4\x7d\x79\xa4\x57\x3f\x94\x41\x44\xbe\x45\x04\x8e\x07\xca\x08\x46\x15\x39\x07\x14\x0f\x0c\x65\x7d\xc6\xde\xa8\xaa\xc8\x30\xaa\xab\x5a\xcb\x71\xee\x4a\xb9\x32\x75\x35\xae\xef\x22\x77\xa9\x39\x2f\x66\x3f\x01\x00\x00\xff\xff\x55\xce\x24\xf7\x69\x01\x00\x00") + +func templates_headHtmlBytes() ([]byte, error) { + return bindataRead( + _templates_headHtml, + "templates/_head.html", + ) +} + +func templates_headHtml() (*asset, error) { + bytes, err := templates_headHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/_head.html", size: 361, mode: os.FileMode(420), modTime: time.Unix(1468764427, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xaa\xae\x2e\x49\xcd\x2d\xc8\x49\x2c\x49\x55\x50\x8a\xcf\x48\x4d\x4c\xd1\xcb\x28\xc9\xcd\x51\xaa\xad\xe5\x52\x00\x82\xf0\xd4\x9c\xe4\xfc\xdc\x54\x85\x92\x7c\x85\xfc\xaa\x52\xbd\xcc\x7c\x1d\x85\x44\x85\x94\xa2\xd2\xbc\xec\xd4\x3c\x85\xd0\x20\x1f\x85\xe2\x8c\xfc\xa2\x92\xd4\xbc\xd4\x22\x3d\x2e\x14\x93\xd2\xf2\xf3\x4b\xe0\x26\x01\x02\x00\x00\xff\xff\x93\x8b\x7b\xb5\x64\x00\x00\x00") + +func templatesIndexHtmlBytes() ([]byte, error) { + return bindataRead( + _templatesIndexHtml, + "templates/index.html", + ) +} + +func templatesIndexHtml() (*asset, error) { + bytes, err := templatesIndexHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/index.html", size: 100, mode: os.FileMode(420), modTime: time.Unix(1468764414, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesRedirectHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x6c\x4f\x41\xcb\x82\x50\x10\xbc\xfb\x2b\x96\xf7\xdd\xf5\xeb\xbe\x3e\xd0\x7c\xf6\x02\xc5\x90\x3d\xd4\x51\x72\xc9\x43\x2a\x98\x05\x21\xfe\xf7\xde\xdb\xea\xd6\x9c\x66\x67\x86\x61\x16\x2d\x95\x85\x0e\x00\xd0\x9a\x24\xf3\xc4\x51\xda\x53\x61\x74\x39\x3e\xb8\x85\x03\x4f\x7d\x33\xf0\x30\x5f\x9f\x18\xbd\x0d\x9f\x8e\xbe\x71\x4c\xab\xec\x04\xe9\x6e\x5b\x15\x55\x1d\xab\xbf\x5c\xa0\x80\xcc\x91\xdc\xf9\x2f\x50\x9f\x62\xbb\xf9\xd5\xea\x54\xb1\xa9\x63\x68\xc7\xf3\xbd\x77\x3a\x74\xcd\x0d\x7a\xc9\x62\x02\xb6\x36\x79\xac\x96\x25\xa4\x66\xba\xf0\xbc\xae\x4a\x77\x3c\x31\x46\x89\x0e\x65\x8d\x1f\xa1\x03\x57\x25\xdf\xbc\x02\x00\x00\xff\xff\xf9\x4e\x03\x95\xd5\x00\x00\x00") + +func templatesRedirectHtmlBytes() ([]byte, error) { + return bindataRead( + _templatesRedirectHtml, + "templates/redirect.html", + ) +} + +func templatesRedirectHtml() (*asset, error) { + bytes, err := templatesRedirectHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/redirect.html", size: 213, mode: os.FileMode(420), modTime: time.Unix(1468764854, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "static/main.css": staticMainCss, + "templates/_foot.html": templates_footHtml, + "templates/_head.html": templates_headHtml, + "templates/index.html": templatesIndexHtml, + "templates/redirect.html": templatesRedirectHtml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "static": &bintree{nil, map[string]*bintree{ + "main.css": &bintree{staticMainCss, map[string]*bintree{}}, + }}, + "templates": &bintree{nil, map[string]*bintree{ + "_foot.html": &bintree{templates_footHtml, map[string]*bintree{}}, + "_head.html": &bintree{templates_headHtml, map[string]*bintree{}}, + "index.html": &bintree{templatesIndexHtml, map[string]*bintree{}}, + "redirect.html": &bintree{templatesRedirectHtml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/web/handler.go b/web/handler.go new file mode 100644 index 0000000..eea25d8 --- /dev/null +++ b/web/handler.go @@ -0,0 +1,176 @@ +package web + +import ( + "encoding/json" + "html/template" + "mime" + "net/url" + "path" + "time" + + "github.com/jimeh/ozu.io/shortener" + "github.com/qiangxue/fasthttp-routing" + "github.com/valyala/fasthttp" +) + +//go:generate go-bindata -pkg web static/... templates/... + +// NewHandler creates a new Handler object. +func NewHandler(s shortener.Shortener) *Handler { + t := template.New("base") + + files, err := AssetDir("templates") + if err != nil { + panic(err) + } + + for _, f := range files { + content, err := Asset("templates/" + f) + if err != nil { + panic(err) + } + + t.New(f).Parse(string(content)) + } + + return &Handler{s, t} +} + +// Handler handle HTTP requests. +type Handler struct { + s shortener.Shortener + t *template.Template +} + +// Index handles requests for root. +func (h *Handler) Index(c *routing.Context) error { + h.template(c, "index.html", nil) + return nil +} + +// NotFound returns a 404 error page. +func (h *Handler) NotFound(c *routing.Context) error { + c.NotFound() + return nil +} + +// Static returns assets serialized via go-bindata +func (h *Handler) Static(c *routing.Context) error { + p := string(c.Path())[1:] + + data, err := Asset(p) + if err != nil { + h.NotFound(c) + return nil + } + + info, _ := AssetInfo(p) + contentType := mime.TypeByExtension(path.Ext(p)) + modTime := info.ModTime().In(time.FixedZone("GMT", 0)) + + c.SetContentType(contentType) + c.Response.Header.Set("Last-Modified", modTime.Format(time.RFC1123)) + c.Write(data) + return nil +} + +// Shorten shortens given URL. +func (h *Handler) Shorten(c *routing.Context) error { + uid, url, err := h.s.Shorten(c.FormValue("url")) + if err != nil { + return h.respondWithError(c, err) + } + + r := h.makeURLResponse(c, uid, url) + return h.respond(c, &r) +} + +// Lookup shortened UID. +func (h *Handler) Lookup(c *routing.Context) error { + uid := c.FormValue("uid") + url, err := h.s.Lookup(uid) + if err != nil { + return h.respondWithError(c, err) + } + + r := h.makeURLResponse(c, uid, url) + return h.respond(c, &r) +} + +// LookupAndRedirect looks up given UID and redirects to it's URL. +func (h *Handler) LookupAndRedirect(c *routing.Context) error { + uid := []byte(c.Param("uid")) + + url, err := h.s.Lookup(uid) + if err != nil { + h.NotFound(c) + return nil + } + + r := h.makeURLResponse(c, uid, url) + + c.Response.Header.Set("Pragma", "no-cache") + c.Response.Header.Set("Expires", "Mon, 01 Jan 1990 00:00:00 GMT") + c.Response.Header.Set("X-XSS-Protection", "1; mode=block") + c.Response.Header.Set("Cache-Control", + "no-cache, no-store, max-age=0, must-revalidate") + c.Redirect(string(url), fasthttp.StatusMovedPermanently) + c.Response.Header.Set("Connection", "close") + c.Response.Header.Set("X-Content-Type-Options", "nosniff") + c.Response.Header.Set("Accept-Ranges", "none") + c.Response.Header.Set("X-Frame-Options", "SAMEORIGIN") + c.Response.Header.Set("Vary", "Accept-Encoding") + + h.template(c, "redirect.html", r) + return nil +} + +func (h *Handler) template(c *routing.Context, name string, data interface{}) { + c.SetContentType("text/html; charset=UTF-8") + h.t.ExecuteTemplate(c, name, data) +} + +func (h *Handler) respond(c *routing.Context, r *URLResponse) error { + resp, err := json.Marshal(r) + if err != nil { + return err + } + + c.SetContentType("application/json") + c.Write(resp) + return nil +} + +func (h *Handler) respondWithError(c *routing.Context, err error) error { + r := ErrorResponse{ + Error: err.Error(), + } + + resp, err := json.Marshal(r) + if err != nil { + return err + } + + c.SetStatusCode(fasthttp.StatusNotFound) + c.SetContentType("application/json") + c.Write(resp) + return nil +} + +func (h *Handler) makeURLResponse(c *routing.Context, uid []byte, url []byte) URLResponse { + return URLResponse{ + UID: string(uid), + URL: h.makeShortURL(c, uid), + Target: string(url), + } +} + +func (h *Handler) makeShortURL(c *routing.Context, uid []byte) string { + shortURL := &url.URL{ + Scheme: "http", + Host: string(c.Host()), + Path: "/" + string(uid), + } + + return shortURL.String() +} diff --git a/web/handlers.go b/web/handlers.go deleted file mode 100644 index 761030b..0000000 --- a/web/handlers.go +++ /dev/null @@ -1,90 +0,0 @@ -package web - -import ( - "encoding/json" - "fmt" - "net/url" - - "github.com/jimeh/ozu.io/shortener" - "github.com/qiangxue/fasthttp-routing" - "github.com/valyala/fasthttp" -) - -// Handlers handle HTTP requests. -type Handlers struct { - s *shortener.Shortener -} - -// Index handles requests for root. -func (h *Handlers) Index(c *routing.Context) error { - c.WriteString("Welcome to ozu.io, a shitty URL shortener.") - return nil -} - -// Shorten shortens given URL. -func (h *Handlers) Shorten(c *routing.Context) error { - c.SetContentType("application/json") - - uid, url, err := h.s.Shorten(c.FormValue("url")) - if err != nil { - c.SetStatusCode(fasthttp.StatusBadRequest) - response, _ := json.Marshal(ErrorResponse{err.Error()}) - c.Write(response) - return nil - } - - h.respondWithShortened(c, uid, url) - return nil -} - -// Lookup shortened UID. -func (h *Handlers) Lookup(c *routing.Context) error { - c.SetContentType("application/json") - - uid := c.FormValue("uid") - url, err := h.s.Lookup(uid) - if err != nil { - c.SetStatusCode(fasthttp.StatusNotFound) - respBytes, _ := json.Marshal(ErrorResponse{err.Error()}) - c.Write(respBytes) - return nil - } - - h.respondWithShortened(c, uid, url) - return nil -} - -// LookupAndRedirect looks up given UID and redirects to it's URL. -func (h *Handlers) LookupAndRedirect(c *routing.Context) error { - uid := c.Param("uid") - url, err := h.s.Lookup([]byte(uid)) - if err != nil { - c.SetStatusCode(fasthttp.StatusNotFound) - fmt.Fprint(c, "404 Not Found") - return nil - } - - c.Redirect(string(url), fasthttp.StatusMovedPermanently) - return nil -} - -func (h *Handlers) respondWithShortened(c *routing.Context, uid []byte, url []byte) { - c.SetStatusCode(fasthttp.StatusOK) - response := ShortenedResponse{ - UID: string(uid), - URL: h.makeShortURL(c, uid), - Target: string(url), - } - respBytes, _ := json.Marshal(response) - c.Write(respBytes) -} - -func (h *Handlers) makeShortURL(c *routing.Context, uid []byte) string { - shortURL := &url.URL{ - Scheme: "http", - Host: string(c.Host()), - Path: "/" + string(uid), - } - - return shortURL.String() -} diff --git a/web/responses.go b/web/responses.go index 676b205..6e8e85c 100644 --- a/web/responses.go +++ b/web/responses.go @@ -2,14 +2,14 @@ package web //go:generate easyjson -all responses.go -// ShortenedResponse contains shortened URL info. -type ShortenedResponse struct { +// URLResponse contains shortened URL info. +type URLResponse struct { UID string `json:"uid"` URL string `json:"url"` Target string `json:"target"` } -// ErrorResponse contains error info. +// ErrorJSONResponse contains error info. type ErrorResponse struct { Error string `json:"error"` } diff --git a/web/responses_easyjson.go b/web/responses_easyjson.go index c333843..e26a960 100644 --- a/web/responses_easyjson.go +++ b/web/responses_easyjson.go @@ -62,7 +62,7 @@ func (v *ErrorResponse) UnmarshalJSON(data []byte) error { func (v *ErrorResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(l, v) } -func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(in *jlexer.Lexer, out *ShortenedResponse) { +func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(in *jlexer.Lexer, out *URLResponse) { if in.IsNull() { in.Skip() return @@ -90,7 +90,7 @@ func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(in * } in.Delim('}') } -func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(out *jwriter.Writer, in ShortenedResponse) { +func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(out *jwriter.Writer, in URLResponse) { out.RawByte('{') first := true _ = first @@ -114,19 +114,19 @@ func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(out out.String(string(in.Target)) out.RawByte('}') } -func (v ShortenedResponse) MarshalJSON() ([]byte, error) { +func (v URLResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(&w, v) return w.Buffer.BuildBytes(), w.Error } -func (v ShortenedResponse) MarshalEasyJSON(w *jwriter.Writer) { +func (v URLResponse) MarshalEasyJSON(w *jwriter.Writer) { easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(w, v) } -func (v *ShortenedResponse) UnmarshalJSON(data []byte) error { +func (v *URLResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(&r, v) return r.Error() } -func (v *ShortenedResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { +func (v *URLResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(l, v) } diff --git a/web/router.go b/web/router.go index c2f43f3..3b45fcf 100644 --- a/web/router.go +++ b/web/router.go @@ -6,14 +6,16 @@ import ( ) // NewRouter creates a new routing.Router with all handlers registered. -func NewRouter(shortener *shortener.Shortener) *routing.Router { - router := routing.New() - handlers := Handlers{shortener} +func NewRouter(s shortener.Shortener) *routing.Router { + r := routing.New() + h := NewHandler(s) - router.Get("/", handlers.Index) - router.Get("/api/shorten", handlers.Shorten) - router.Get("/api/lookup", handlers.Lookup) - router.Get("/", handlers.LookupAndRedirect) + r.Get("/", h.Index) + r.Get("/api/shorten", h.Shorten) + r.Get("/api/lookup", h.Lookup) + r.Get("/static/*", h.Static) + r.Get("/", h.LookupAndRedirect) + r.Get("/*", h.NotFound) - return router + return r } diff --git a/web/server.go b/web/server.go new file mode 100644 index 0000000..7dd2028 --- /dev/null +++ b/web/server.go @@ -0,0 +1,12 @@ +package web + +import ( + "github.com/jimeh/ozu.io/shortener" + "github.com/valyala/fasthttp" +) + +// NewServer returns a new fasthttp.Server with all routes configured. +func NewServer(s shortener.Shortener) *fasthttp.Server { + r := NewRouter(s) + return &fasthttp.Server{Handler: r.HandleRequest} +} diff --git a/web/static/main.css b/web/static/main.css new file mode 100644 index 0000000..2ae8aab --- /dev/null +++ b/web/static/main.css @@ -0,0 +1,3 @@ +body { + font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +} diff --git a/web/templates/_foot.html b/web/templates/_foot.html new file mode 100644 index 0000000..b605728 --- /dev/null +++ b/web/templates/_foot.html @@ -0,0 +1,2 @@ + + diff --git a/web/templates/_head.html b/web/templates/_head.html new file mode 100644 index 0000000..f2d97ef --- /dev/null +++ b/web/templates/_head.html @@ -0,0 +1,12 @@ + + + + + + + ozu.io + + + + + diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000..c6aa07a --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,3 @@ +{{template "_head.html"}} + Welcome to ozu.io, a drunken URL shortener. +{{template "_foot.html"}} diff --git a/web/templates/redirect.html b/web/templates/redirect.html new file mode 100644 index 0000000..a96601a --- /dev/null +++ b/web/templates/redirect.html @@ -0,0 +1,9 @@ + + + Moved Permanently + + +

Moved Permanently

+ The document has moved here. + +