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"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"qurl/pages"
 | 
						"qurl/pages"
 | 
				
			||||||
	"qurl/static"
 | 
					 | 
				
			||||||
	"qurl/storage"
 | 
						"qurl/storage"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -54,22 +53,15 @@ func main() {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mux := http.NewServeMux()
 | 
						root := &pages.RootHandler{Storage: stor}
 | 
				
			||||||
	mux.Handle("/index.html", &static.StaticContent{Content: "index.html"})
 | 
						err = root.Init()
 | 
				
			||||||
	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()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mux.Handle("/submit.html", submit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fmt.Fprintf(os.Stdout, "qurl listening .. \n")
 | 
						fmt.Fprintf(os.Stdout, "qurl listening .. \n")
 | 
				
			||||||
	err = http.Serve(listen, mux)
 | 
						err = http.Serve(listen, root)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		fmt.Fprintf(os.Stderr, "Serve error: %s\n", err.Error())
 | 
							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 (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"qurl/qurl"
 | 
						"qurl/qurl"
 | 
				
			||||||
	"qurl/static"
 | 
					 | 
				
			||||||
	"qurl/storage"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SubmitHandler struct {
 | 
					 | 
				
			||||||
	Storage  storage.Storage
 | 
					 | 
				
			||||||
	template *template.Template
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type submitPage struct {
 | 
					type submitPage struct {
 | 
				
			||||||
	Message string
 | 
						Message string
 | 
				
			||||||
	URL     string
 | 
						URL     string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ctx *SubmitHandler) Init() error {
 | 
					func (ctx *RootHandler) ServeSubmit(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	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) {
 | 
					 | 
				
			||||||
	var pg submitPage
 | 
						var pg submitPage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	u := r.FormValue("url")
 | 
						u := r.FormValue("url")
 | 
				
			||||||
@ -79,7 +61,7 @@ func (ctx *SubmitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var buf bytes.Buffer
 | 
						var buf bytes.Buffer
 | 
				
			||||||
	err := ctx.template.Execute(&buf, pg)
 | 
						err := ctx.submit.Execute(&buf, pg)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		http.Error(w, fmt.Sprintf("Template execute error: %s", err.Error()),
 | 
							http.Error(w, fmt.Sprintf("Template execute error: %s", err.Error()),
 | 
				
			||||||
			http.StatusInternalServerError)
 | 
								http.StatusInternalServerError)
 | 
				
			||||||
 | 
				
			|||||||
@ -12,14 +12,58 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type StaticContent struct {
 | 
					type StaticContent struct {
 | 
				
			||||||
	ContentType string
 | 
					 | 
				
			||||||
	Content     string
 | 
						Content     string
 | 
				
			||||||
 | 
						ContentType string
 | 
				
			||||||
	ETag        string
 | 
						ETag        string
 | 
				
			||||||
	GZIPContent []byte
 | 
						GZIPContent []byte
 | 
				
			||||||
	GZIPETag    string
 | 
						GZIPETag    string
 | 
				
			||||||
	DisableGZIP bool
 | 
						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) {
 | 
					func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	// By default, don't use gzip
 | 
						// By default, don't use gzip
 | 
				
			||||||
	useGZIP := false
 | 
						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")
 | 
							useGZIP = strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If gzip is enabled, and there's no gzip etag,
 | 
						// Use the correct 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
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var localETag string
 | 
						var localETag string
 | 
				
			||||||
	if useGZIP {
 | 
						if useGZIP {
 | 
				
			||||||
		localETag = ctx.GZIPETag
 | 
							localETag = ctx.GZIPETag
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// Generate an ETag for content if necessary
 | 
					 | 
				
			||||||
		if ctx.ETag == "" {
 | 
					 | 
				
			||||||
			ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		localETag = ctx.ETag
 | 
							localETag = ctx.ETag
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,17 +89,6 @@ func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	w.Header().Set("ETag", localETag)
 | 
						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)
 | 
						w.Header().Set("Content-Type", ctx.ContentType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Finally, write our content
 | 
						// Finally, write our content
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user