Refactoring Code

This commit is contained in:
Ishan Jain 2017-10-03 16:38:27 +05:30
parent 5fbb1e3b4c
commit fc7d3dd013
5 changed files with 273 additions and 136 deletions

30
auth/auth.go Normal file
View file

@ -0,0 +1,30 @@
package auth
import (
"net/http"
"github.com/ishanjain28/pogo/common"
)
func RequireAuthorization() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
if usr := DecryptSession(r); usr != nil {
rc.User = usr
return nil
}
return &common.HTTPError{
Message: "Unauthorized!",
StatusCode: http.StatusUnauthorized,
}
}
}
func CreateSession() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
return nil
}
}
func DecryptSession(r *http.Request) *common.User {
return nil
}

66
common/common.go Normal file
View file

@ -0,0 +1,66 @@
package common
import (
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
)
// Handler is the signature of HTTP Handler that is passed to Handle function
type Handler func(rc *RouterContext, w http.ResponseWriter, r *http.Request) *HTTPError
// HTTPError is any error that occurs in middlewares or the code that handles HTTP Frontend of application
// Message is logged to console and Status Code is sent in response
// If a Middleware sends an HTTPError, No middlewares further up in chain are executed
type HTTPError struct {
// Message to log in console
Message string
// Status code that'll be sent in response
StatusCode int
}
// RouterContext contains any information to be shared with middlewares.
type RouterContext struct {
User *User
}
// User struct denotes the data is stored in the cookie
type User struct {
Name string `json:"name"`
}
// ReadAndServeFile reads the file from specified location and sends it in response
func ReadAndServeFile(name string, w http.ResponseWriter) *HTTPError {
f, err := os.Open(name)
if err != nil {
if os.IsNotExist(err) {
return &HTTPError{
Message: fmt.Sprintf("%s not found", name),
StatusCode: http.StatusNotFound,
}
}
return &HTTPError{
Message: fmt.Sprintf("error in reading %s: %v\n", name, err),
StatusCode: http.StatusInternalServerError,
}
}
defer f.Close()
stats, err := f.Stat()
if err != nil {
log.Printf("error in fetching %s's stats: %v\n", name, err)
} else {
w.Header().Add("Content-Length", strconv.FormatInt(stats.Size(), 10))
}
_, err = io.Copy(w, f)
if err != nil {
log.Printf("error in copying %s to response: %v\n", name, err)
}
return nil
}

148
router/router.go Normal file
View file

@ -0,0 +1,148 @@
package router
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/ishanjain28/pogo/auth"
"github.com/ishanjain28/pogo/common"
)
type NewConfig struct {
Name string
Host string
Email string
Description string
Image string
PodcastURL string
}
// Handle takes multiple Handler and executes them in a serial order starting from first to last.
// In case, Any middle ware returns an error, The error is logged to console and sent to the user, Middlewares further up in chain are not executed.
func Handle(handlers ...common.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rc := &common.RouterContext{}
for _, handler := range handlers {
err := handler(rc, w, r)
if err != nil {
log.Printf("%v", err)
w.Write([]byte(http.StatusText(err.StatusCode)))
return
}
}
})
}
func Init() *mux.Router {
r := mux.NewRouter()
// "Static" paths
r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/web/static"))))
r.PathPrefix("/download/").Handler(http.StripPrefix("/download/", http.FileServer(http.Dir("podcasts"))))
// Paths that require specific handlers
r.Handle("/", Handle(
rootHandler(),
)).Methods("GET")
r.Handle("/rss", Handle(
rootHandler(),
)).Methods("GET")
r.Handle("/json", Handle(
rootHandler(),
)).Methods("GET")
// Authenticated endpoints should be passed to BasicAuth()
// first
r.Handle("/admin", Handle(
auth.RequireAuthorization(),
adminHandler(),
))
// r.HandleFunc("/admin/publish", BasicAuth(CreateEpisode))
// r.HandleFunc("/admin/delete", BasicAuth(RemoveEpisode))
// r.HandleFunc("/admin/css", BasicAuth(CustomCss))
r.Handle("/setup", Handle(
serveSetup(),
)).Methods("GET", "POST")
return r
}
// Handles /, /feed and /json endpoints
func rootHandler() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
var file string
switch r.URL.Path {
case "/rss":
w.Header().Set("Content-Type", "application/rss+xml")
file = "assets/web/feed.rss"
case "/json":
w.Header().Set("Content-Type", "application/json")
file = "assets/web/feed.json"
case "/":
w.Header().Set("Content-Type", "text/html")
file = "assets/web/index.html"
default:
return &common.HTTPError{
Message: fmt.Sprintf("%s: Not Found", r.URL.Path),
StatusCode: http.StatusNotFound,
}
}
return common.ReadAndServeFile(file, w)
}
}
func adminHandler() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
return common.ReadAndServeFile("assets/web/admin.html", w)
}
}
// Serve setup.html and config parameters
func serveSetup() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
if r.Method == "GET" {
return common.ReadAndServeFile("assets/web/setup.html", w)
}
r.ParseMultipartForm(32 << 20)
// Parse form and convert to JSON
cnf := NewConfig{
strings.Join(r.Form["podcastname"], ""), // Podcast name
strings.Join(r.Form["podcasthost"], ""), // Podcast host
strings.Join(r.Form["podcastemail"], ""), // Podcast host email
"", // Podcast image
"", // Podcast location
"", // Podcast location
}
b, err := json.Marshal(cnf)
if err != nil {
panic(err)
}
ioutil.WriteFile("assets/config/config.json", b, 0644)
w.Write([]byte("Done"))
return nil
}
}
func redirectHandler() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
return nil
}
}

View file

@ -1,50 +0,0 @@
package main
import (
"io/ioutil"
"net/http"
"encoding/json"
"strings"
)
type NewConfig struct {
Name string
Host string
Email string
Description string
Image string
PodcastUrl string
}
// Serve setup.html and config parameters
func ServeSetup(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
data, err := ioutil.ReadFile("assets/web/setup.html")
if err != nil {
panic(err)
}
w.Write(data)
}
if r.Method == "POST" {
r.ParseMultipartForm(32 << 20)
// Parse form and convert to JSON
cnf := NewConfig{
strings.Join(r.Form["podcastname"], ""), // Podcast name
strings.Join(r.Form["podcasthost"], ""), // Podcast host
strings.Join(r.Form["podcastemail"], ""), // Podcast host email
strings.Join(r.Form["podcastdescription"], ""), // Podcast Description
"", // Podcast image
"", // Podcast location
}
b, err := json.Marshal(cnf)
if err != nil {
panic(err)
}
ioutil.WriteFile("assets/config/config.json", b, 0644)
w.Write([]byte("Done"))
}
}

View file

@ -8,49 +8,12 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/ishanjain28/pogo/router"
) )
// Prints out contents of feed.rss
func RssHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/rss+xml")
w.WriteHeader(http.StatusOK)
data, err := ioutil.ReadFile("assets/web/feed.rss")
if err != nil {
panic(err)
}
w.Header().Set("Content-Length", fmt.Sprint(len(data)))
fmt.Fprint(w, string(data))
}
// Does the same as above method, only with the JSON feed data
func JsonHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
data, err := ioutil.ReadFile("assets/web/feed.json")
if err != nil {
panic(err)
}
w.Header().Set("Content-Length", fmt.Sprint(len(data)))
fmt.Fprint(w, string(data))
}
// Serve up homepage
func HomeHandler(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadFile("assets/web/index.html")
if err == nil {
w.Write(data)
} else {
w.WriteHeader(500)
w.Write([]byte("Error500 - " + http.StatusText(500)))
}
}
// Authenticate user using basic webserver authentication // Authenticate user using basic webserver authentication
// @TODO: Replace this with a different for of _more secure_ // @TODO: Replace this with a different for of _more secure_
// authentication that we can POST to instead. // authentication that we can POST to instead.
@ -58,63 +21,43 @@ func HomeHandler(w http.ResponseWriter, r *http.Request) {
* Code from stackoverflow by user Timmmm * Code from stackoverflow by user Timmmm
* https://stackoverflow.com/questions/21936332/idiomatic-way-of-requiring-http-basic-auth-in-go/39591234#39591234 * https://stackoverflow.com/questions/21936332/idiomatic-way-of-requiring-http-basic-auth-in-go/39591234#39591234
*/ */
func BasicAuth(handler http.HandlerFunc) http.HandlerFunc { // func BasicAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { // return func(w http.ResponseWriter, r *http.Request) {
realm := "Login to Pogo admin interface" // realm := "Login to Pogo admin interface"
user, pass, ok := r.BasicAuth() // user, pass, ok := r.BasicAuth()
if !AuthUser(user,pass) || !ok { // if !AuthUser(user, pass) || !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`) // w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401) // w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n")) // w.Write([]byte("Unauthorised.\n"))
return // return
} // }
handler(w, r) // handler(w, r)
} // }
} // }
// Handler for serving up admin page // // Handler for serving up admin page
func AdminHandler(w http.ResponseWriter, r *http.Request) { // func AdminHandler(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadFile("assets/web/admin.html") // data, err := ioutil.ReadFile("assets/web/admin.html")
if err == nil { // if err == nil {
w.Write(data) // w.Write(data)
} else { // } else {
w.WriteHeader(500) // w.WriteHeader(500)
w.Write([]byte("500 Something went wrong - " + http.StatusText(500))) // w.Write([]byte("500 Something went wrong - " + http.StatusText(500)))
} // }
} // }
// Main function that defines routes // Main function that defines routes
func main() { func main() {
// Start the watch() function in generate_rss.go, which // Start the watch() function in generate_rss.go, which
// watches for file changes and regenerates the feed // watches for file changes and regenerates the feed
go watch() // go watch()
// Define routes // Define routes
r := mux.NewRouter() // We're live
r := router.Init()
// "Static" paths fmt.Println("Listening on port :3000")
r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/web/static")))) log.Fatal(http.ListenAndServe(":3000", r))
r.PathPrefix("/download/").Handler(http.StripPrefix("/download/", http.FileServer(http.Dir("podcasts"))))
// Paths that require specific handlers
http.Handle("/", r) // Pass everything to gorilla mux
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/rss", RssHandler)
r.HandleFunc("/json", JsonHandler)
// Authenticated endpoints should be passed to BasicAuth()
// first
r.HandleFunc("/admin", BasicAuth(AdminHandler))
r.HandleFunc("/admin/publish", BasicAuth(CreateEpisode))
r.HandleFunc("/admin/delete", BasicAuth(RemoveEpisode))
r.HandleFunc("/admin/css", BasicAuth(CustomCss))
r.HandleFunc("/setup", ServeSetup)
// We're live!
fmt.Println("Listening on port :8000")
log.Fatal(http.ListenAndServe(":8000", r))
} }