From 76145b396965b571b92342e8ab050c2aac46bbec Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Mon, 11 Jul 2016 00:51:48 +0100 Subject: [PATCH] Get basic HTTP server working --- main.go | 12 +++- web/responses.go | 15 +++++ web/responses_easyjson.go | 132 ++++++++++++++++++++++++++++++++++++++ web/router.go | 97 +++++++++++++++++++++++++++- 4 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 web/responses.go create mode 100644 web/responses_easyjson.go diff --git a/main.go b/main.go index b9f0489..143252c 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,13 @@ package main import ( - "fmt" "log" + "os" "github.com/jimeh/ozu.io/shortner" "github.com/jimeh/ozu.io/storage/goleveldbstore" "github.com/jimeh/ozu.io/web" + "github.com/valyala/fasthttp" ) func main() { @@ -14,8 +15,15 @@ func main() { if err != nil { log.Fatal(err) } + defer store.Close() shortner := shortner.New(store) router := web.NewRouter(shortner) - fmt.Println(router) + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + log.Fatal(fasthttp.ListenAndServe(":"+port, router.HandleRequest)) } diff --git a/web/responses.go b/web/responses.go new file mode 100644 index 0000000..f34bb34 --- /dev/null +++ b/web/responses.go @@ -0,0 +1,15 @@ +package web + +//go:generate easyjson -all responses.go + +// ShortenedResponse contains shortened URL info. +type ShortenedResponse struct { + UID string `json:"uid"` + ShortURL string `json:"short_url"` + URL string `json:"url"` +} + +// ErrorResponse contains error info. +type ErrorResponse struct { + Error string `json:"error"` +} diff --git a/web/responses_easyjson.go b/web/responses_easyjson.go new file mode 100644 index 0000000..fb8b8f1 --- /dev/null +++ b/web/responses_easyjson.go @@ -0,0 +1,132 @@ +// AUTOGENERATED FILE: easyjson marshaller/unmarshallers. + +package web + +import ( + json "encoding/json" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +var _ = json.RawMessage{} // suppress unused package warning + +func easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(in *jlexer.Lexer, out *ErrorResponse) { + if in.IsNull() { + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "error": + out.Error = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') +} +func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(out *jwriter.Writer, in ErrorResponse) { + out.RawByte('{') + first := true + _ = first + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"error\":") + out.String(string(in.Error)) + out.RawByte('}') +} +func (v ErrorResponse) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(&w, v) + return w.Buffer.BuildBytes(), w.Error +} +func (v ErrorResponse) MarshalEasyJSON(w *jwriter.Writer) { + easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ErrorResponse(w, v) +} +func (v *ErrorResponse) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ErrorResponse(&r, v) + return r.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) { + if in.IsNull() { + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "uid": + out.UID = string(in.String()) + case "short_url": + out.ShortURL = string(in.String()) + case "url": + out.URL = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') +} +func easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(out *jwriter.Writer, in ShortenedResponse) { + out.RawByte('{') + first := true + _ = first + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"uid\":") + out.String(string(in.UID)) + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"short_url\":") + out.String(string(in.ShortURL)) + if !first { + out.RawByte(',') + } + first = false + out.RawString("\"url\":") + out.String(string(in.URL)) + out.RawByte('}') +} +func (v ShortenedResponse) 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) { + easyjson_559270ae_encode_github_com_jimeh_ozu_io_web_ShortenedResponse(w, v) +} +func (v *ShortenedResponse) 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) { + easyjson_559270ae_decode_github_com_jimeh_ozu_io_web_ShortenedResponse(l, v) +} diff --git a/web/router.go b/web/router.go index 3ff95d2..691cfda 100644 --- a/web/router.go +++ b/web/router.go @@ -1,12 +1,103 @@ package web import ( - "github.com/buaazp/fasthttprouter" + "encoding/json" + "fmt" + "net/url" + "github.com/jimeh/ozu.io/shortner" + "github.com/qiangxue/fasthttp-routing" + "github.com/valyala/fasthttp" ) -func NewRouter(shortner *shortner.Shortner) *fasthttprouter.Router { - router := fasthttprouter.New() +// NewRouter creates a new fasthttprouter.Router with all handlers registered. +func NewRouter(shortner *shortner.Shortner) *routing.Router { + router := routing.New() + handlers := Handlers{shortner} + + router.Get("/", handlers.Index) + router.Get("/api/shorten", handlers.Shorten) + router.Get("/api/lookup", handlers.Lookup) + router.Get("/", handlers.LookupAndRedirect) return router } + +// Handlers handle HTTP requests. +type Handlers struct { + s *shortner.Shortner +} + +// Index handles requests for root. +func (h *Handlers) Index(c *routing.Context) error { + c.WriteString("Welcome to ozu.io, a shitty URL shortner.") + 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), + ShortURL: h.makeShortURL(c, uid), + URL: 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() +}