Migrated all http mechanics to a central handler, changed static
content to all be front-loaded and thread safe
This commit is contained in:
		
							
								
								
									
										16
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								main.go
									
									
									
									
									
								
							@ -7,7 +7,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"qurl/pages"
 | 
			
		||||
	"qurl/static"
 | 
			
		||||
	"qurl/storage"
 | 
			
		||||
	"runtime"
 | 
			
		||||
)
 | 
			
		||||
@ -54,22 +53,15 @@ func main() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	mux.Handle("/index.html", &static.StaticContent{Content: "index.html"})
 | 
			
		||||
	mux.Handle("/favicon.ico", &static.StaticContent{Content: "favicon.ico"})
 | 
			
		||||
	mux.Handle("/qurl.css", &static.StaticContent{Content: "qurl.css"})
 | 
			
		||||
 | 
			
		||||
	submit := &pages.SubmitHandler{Storage: stor}
 | 
			
		||||
	err = submit.Init()
 | 
			
		||||
	root := &pages.RootHandler{Storage: stor}
 | 
			
		||||
	err = root.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "Submit init error: %s\n", err.Error())
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "Handler Init Error: %s\n", err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mux.Handle("/submit.html", submit)
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(os.Stdout, "qurl listening .. \n")
 | 
			
		||||
	err = http.Serve(listen, mux)
 | 
			
		||||
	err = http.Serve(listen, root)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "Serve error: %s\n", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								pages/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								pages/root.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
package pages
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"qurl/static"
 | 
			
		||||
	"qurl/storage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RootHandler struct {
 | 
			
		||||
	Storage storage.Storage
 | 
			
		||||
 | 
			
		||||
	index  *static.StaticContent
 | 
			
		||||
	css    *static.StaticContent
 | 
			
		||||
	favi   *static.StaticContent
 | 
			
		||||
	submit *template.Template
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	fname := r.URL.Path
 | 
			
		||||
 | 
			
		||||
	switch fname {
 | 
			
		||||
	case "/index.html":
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case "/":
 | 
			
		||||
		ctx.index.ServeHTTP(w, r)
 | 
			
		||||
 | 
			
		||||
	case "/submit.html":
 | 
			
		||||
		ctx.ServeSubmit(w, r)
 | 
			
		||||
 | 
			
		||||
	case "/qurl.css":
 | 
			
		||||
		ctx.css.ServeHTTP(w, r)
 | 
			
		||||
 | 
			
		||||
	case "/favicon.ico":
 | 
			
		||||
		ctx.favi.ServeHTTP(w, r)
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		fmt.Printf("Path: %s\n", fname)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *RootHandler) Init() error {
 | 
			
		||||
	// Initialize the static content object for the index page
 | 
			
		||||
	index := &static.StaticContent{Content: "index.html"}
 | 
			
		||||
	err := index.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ctx.index = index
 | 
			
		||||
 | 
			
		||||
	// Initialize the static content object for the css
 | 
			
		||||
	css := &static.StaticContent{Content: "qurl.css"}
 | 
			
		||||
	err = css.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ctx.css = css
 | 
			
		||||
 | 
			
		||||
	// Initialize the static content object for the css
 | 
			
		||||
	favi := &static.StaticContent{Content: "favicon.ico"}
 | 
			
		||||
	err = favi.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ctx.favi = favi
 | 
			
		||||
 | 
			
		||||
	// Initialize submit page template
 | 
			
		||||
	ctx.submit = template.New("submit.html")
 | 
			
		||||
	_, err = ctx.submit.Parse(string(static.Assets["submit.html"]))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -3,36 +3,18 @@ package pages
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"qurl/qurl"
 | 
			
		||||
	"qurl/static"
 | 
			
		||||
	"qurl/storage"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SubmitHandler struct {
 | 
			
		||||
	Storage  storage.Storage
 | 
			
		||||
	template *template.Template
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type submitPage struct {
 | 
			
		||||
	Message string
 | 
			
		||||
	URL     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *SubmitHandler) Init() error {
 | 
			
		||||
	ctx.template = template.New("submit.html")
 | 
			
		||||
 | 
			
		||||
	_, err := ctx.template.Parse(string(static.Assets["submit.html"]))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *SubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
func (ctx *RootHandler) ServeSubmit(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	var pg submitPage
 | 
			
		||||
 | 
			
		||||
	u := r.FormValue("url")
 | 
			
		||||
@ -79,7 +61,7 @@ func (ctx *SubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	err := ctx.template.Execute(&buf, pg)
 | 
			
		||||
	err := ctx.submit.Execute(&buf, pg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, fmt.Sprintf("Template execute error: %s", err.Error()),
 | 
			
		||||
			http.StatusInternalServerError)
 | 
			
		||||
 | 
			
		||||
@ -12,14 +12,58 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type StaticContent struct {
 | 
			
		||||
	ContentType string
 | 
			
		||||
	Content     string
 | 
			
		||||
	ContentType string
 | 
			
		||||
	ETag        string
 | 
			
		||||
	GZIPContent []byte
 | 
			
		||||
	GZIPETag    string
 | 
			
		||||
	DisableGZIP bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *StaticContent) Init() error {
 | 
			
		||||
	// Do we have a content type? If not, initialize it
 | 
			
		||||
	if ctx.ContentType == "" {
 | 
			
		||||
		ext := path.Ext(ctx.Content)
 | 
			
		||||
		ctx.ContentType = mime.TypeByExtension(ext)
 | 
			
		||||
		// Fallback to default mime type
 | 
			
		||||
		if ctx.ContentType == "" {
 | 
			
		||||
			ctx.ContentType = "application/octet-stream"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Do we have an etag? If not, generate one
 | 
			
		||||
	if ctx.ETag == "" {
 | 
			
		||||
		ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If gzip is allowed and we have no gzip etag, generate
 | 
			
		||||
	// gzip content and etag
 | 
			
		||||
	if !ctx.DisableGZIP && ctx.GZIPETag == "" {
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
 | 
			
		||||
		defer gz.Close()
 | 
			
		||||
 | 
			
		||||
		if _, err := gz.Write(Assets[ctx.Content]); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := gz.Flush(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check if GZIP actually resulted in a smaller file
 | 
			
		||||
		if buf.Len() < len(Assets[ctx.Content]) {
 | 
			
		||||
			ctx.GZIPContent = buf.Bytes()
 | 
			
		||||
			ctx.GZIPETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
 | 
			
		||||
		} else {
 | 
			
		||||
			// If gzip turns out to be ineffective, disable it
 | 
			
		||||
			ctx.DisableGZIP = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	// By default, don't use gzip
 | 
			
		||||
	useGZIP := false
 | 
			
		||||
@ -29,45 +73,11 @@ func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		useGZIP = strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If gzip is enabled, and there's no gzip etag,
 | 
			
		||||
	// generate the gzip'd content plus the etag
 | 
			
		||||
	if useGZIP && len(ctx.GZIPETag) == 0 {
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
 | 
			
		||||
		gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
 | 
			
		||||
		defer gz.Close()
 | 
			
		||||
 | 
			
		||||
		if _, err := gz.Write(Assets[ctx.Content]); err != nil {
 | 
			
		||||
			http.Error(w, fmt.Sprintf("GZIP write error: %s", err.Error()),
 | 
			
		||||
				http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := gz.Flush(); err != nil {
 | 
			
		||||
			http.Error(w, fmt.Sprintf("GZIP flush error: %s", err.Error()),
 | 
			
		||||
				http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check if GZIP actually resulted in a smaller file
 | 
			
		||||
		if buf.Len() < len(Assets[ctx.Content]) {
 | 
			
		||||
			ctx.GZIPContent = buf.Bytes()
 | 
			
		||||
			ctx.GZIPETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
 | 
			
		||||
		} else {
 | 
			
		||||
			// If gzip turns out to be ineffective, disable it
 | 
			
		||||
			ctx.DisableGZIP = true
 | 
			
		||||
			useGZIP = false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Use the correct etag
 | 
			
		||||
	var localETag string
 | 
			
		||||
	if useGZIP {
 | 
			
		||||
		localETag = ctx.GZIPETag
 | 
			
		||||
	} else {
 | 
			
		||||
		// Generate an ETag for content if necessary
 | 
			
		||||
		if ctx.ETag == "" {
 | 
			
		||||
			ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
 | 
			
		||||
		}
 | 
			
		||||
		localETag = ctx.ETag
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -79,17 +89,6 @@ func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("ETag", localETag)
 | 
			
		||||
 | 
			
		||||
	// Check the content type, if we don't already
 | 
			
		||||
	// have one, make one
 | 
			
		||||
	if ctx.ContentType == "" {
 | 
			
		||||
		ext := path.Ext(ctx.Content)
 | 
			
		||||
		ctx.ContentType = mime.TypeByExtension(ext)
 | 
			
		||||
		// Fallback to default mime type
 | 
			
		||||
		if ctx.ContentType == "" {
 | 
			
		||||
			ctx.ContentType = "application/octet-stream"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", ctx.ContentType)
 | 
			
		||||
 | 
			
		||||
	// Finally, write our content
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user