diff options
| author | Horus3 | 2015-05-11 23:47:47 +0200 |
|---|---|---|
| committer | Horus3 | 2015-05-11 23:47:47 +0200 |
| commit | 5986015358e5b7cf523122bbdb6831dccf7ca306 (patch) | |
| tree | 6b0f5b5b8b80bef7f6a9f286298e8d06080cad84 | |
| parent | 9c757388f1974b32a2f88cf36b709a160491baf8 (diff) | |
| download | uhttpd-5986015358e5b7cf523122bbdb6831dccf7ca306.tar.gz | |
Some fixes, shows 404 page on directory not found.
| -rw-r--r-- | main.go | 29 | ||||
| -rw-r--r-- | recorder.go | 72 | ||||
| -rw-r--r-- | template.go | 166 |
3 files changed, 168 insertions, 99 deletions
@@ -10,24 +10,20 @@ import ( "strings" ) -func accessLog(h http.Handler) http.Handler { +func accessLog(h http.Handler, quiet bool) http.Handler { t := &TemplateHandler{handler: h} - //t := &TemplateHandler{} fn := func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.URL.Path, r.RemoteAddr) + if !quiet { + log.Println(r.Method, r.URL.Path, r.RemoteAddr) + } t.ServeHTTP(w, r) } return http.HandlerFunc(fn) } -/* -func TemplateHandler(h http.Handler) http.Handler { - h.handler -} -*/ - -func uploadHandler(dir string) http.Handler { +func uploadHandler(dir string, quiet bool) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Server", "uhttpd") file, header, err := r.FormFile("file") if err != nil { @@ -60,7 +56,9 @@ func uploadHandler(dir string) http.Handler { return } - log.Println(r.Method, r.URL.Path, header.Filename, r.RemoteAddr) + if !quiet { + log.Println(r.Method, r.URL.Path, header.Filename, r.RemoteAddr) + } w.Write([]byte("Uploaded " + header.Filename)) } return http.HandlerFunc(fn) @@ -72,6 +70,7 @@ func main() { dir_f := flag.String("dir", ".", "Directory to serve.") disallow_upl_f := flag.Bool("disallow-upload", false, "Disallow uploads to /upload.") upl_dir_f := flag.String("upload-dir", ".", "Directory to store uploaded files.") + quiet_f := flag.Bool("quiet", false, "Be quiet, suppress output.") flag.Parse() @@ -80,13 +79,15 @@ func main() { port = *port_f } - fmt.Println("Starting uhttpd serving \"" + *dir_f + "\" on " + *ip_f + ":" + port + ".") + if !*quiet_f { + fmt.Println("Starting uhttpd serving \"" + *dir_f + "\" on " + *ip_f + ":" + port + ".") + } mux := http.NewServeMux() if !*disallow_upl_f { os.MkdirAll(*upl_dir_f, 0755) - mux.Handle("/upload", uploadHandler(*upl_dir_f)) + mux.Handle("/upload", uploadHandler(*upl_dir_f, *quiet_f)) } - mux.Handle("/", accessLog(http.FileServer(http.Dir(*dir_f)))) + mux.Handle("/", accessLog(http.FileServer(http.Dir(*dir_f)), *quiet_f)) log.Fatal(http.ListenAndServe(*ip_f+":"+port, mux)) } diff --git a/recorder.go b/recorder.go new file mode 100644 index 0000000..90ef1a7 --- /dev/null +++ b/recorder.go @@ -0,0 +1,72 @@ +package main + +/* + Copy from net/http/httptest + Please read there for the license. +*/ + +import ( + "bytes" + "net/http" +) + +// ResponseRecorder is an implementation of http.ResponseWriter that +// records its mutations for later inspection in tests. +type ResponseRecorder struct { + Code int // the HTTP response code from WriteHeader + HeaderMap http.Header // the HTTP response headers + Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to + Flushed bool + + wroteHeader bool +} + +// NewRecorder returns an initialized ResponseRecorder. +func NewRecorder() *ResponseRecorder { + return &ResponseRecorder{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + Code: 200, + } +} + +// DefaultRemoteAddr is the default remote address to return in RemoteAddr if +// an explicit DefaultRemoteAddr isn't set on ResponseRecorder. +const DefaultRemoteAddr = "1.2.3.4" + +// Header returns the response headers. +func (rw *ResponseRecorder) Header() http.Header { + m := rw.HeaderMap + if m == nil { + m = make(http.Header) + rw.HeaderMap = m + } + return m +} + +// Write always succeeds and writes to rw.Body, if not nil. +func (rw *ResponseRecorder) Write(buf []byte) (int, error) { + if !rw.wroteHeader { + rw.WriteHeader(200) + } + if rw.Body != nil { + rw.Body.Write(buf) + } + return len(buf), nil +} + +// WriteHeader sets rw.Code. +func (rw *ResponseRecorder) WriteHeader(code int) { + if !rw.wroteHeader { + rw.Code = code + } + rw.wroteHeader = true +} + +// Flush sets rw.Flushed to true. +func (rw *ResponseRecorder) Flush() { + if !rw.wroteHeader { + rw.WriteHeader(200) + } + rw.Flushed = true +} diff --git a/template.go b/template.go index c90b13c..017690d 100644 --- a/template.go +++ b/template.go @@ -4,7 +4,6 @@ import ( "html/template" "log" "net/http" - "net/http/httptest" "strings" ) @@ -14,44 +13,69 @@ type TemplateHandler struct { func (t *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Server", "uhttpd") + // We use our custom handler to prettify directories. Files are served via default. - if strings.HasSuffix(r.URL.Path, "/") { - r.Header.Del("If-Modified-Since") - rec := httptest.NewRecorder() + if !strings.HasSuffix(r.URL.Path, "/") { + t.handler.ServeHTTP(w, r) + return + } - defer rec.Body.Reset() + rec := NewRecorder() - // passing the recorder instead of the real ResponseWriter - t.handler.ServeHTTP(rec, r) + defer rec.Body.Reset() - // we copy the original headers first - for k, v := range rec.Header() { - w.Header()[k] = v - } + // passing the recorder instead of the real ResponseWriter + t.handler.ServeHTTP(rec, r) - // BUG: Sometimes we get a 0 byte response - if rec.Body.Len() == 0 { - log.Println("Error: Empty body. Requested:", r.URL.Path) - } + // let the standard lib handle all the caching + if rec.Code > 300 && rec.Code < 400 { + log.Println("Code: ", rec.Code) + t.handler.ServeHTTP(w, r) + return + } - // we serve a file instead of a html page - if !strings.Contains(w.Header().Get("Content-Type"), "text/html") { - w.Write(rec.Body.Bytes()) + // we copy the original headers first + for k, v := range rec.Header() { + w.Header()[k] = v + } + + // not found + if rec.Code == 404 { + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(404) + tmpl := template.New("404") + tmpl, err := tmpl.Parse(get404()) + if err != nil { + log.Println(err.Error()) + w.WriteHeader(500) + w.Write([]byte(err.Error())) return } - data := rec.Body.String() - - // we serve the directoy page - if strings.HasPrefix(data, "<pre>") { - execTemplate(w, r, data) - } else { - w.Write(rec.Body.Bytes()) + err = tmpl.Execute(w, struct{ URL string }{URL: r.URL.Path}) + if err != nil { + log.Println(err.Error()) + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return } + return + } + // we serve a file instead of a html page + if !strings.Contains(w.Header().Get("Content-Type"), "text/html") { + w.Write(rec.Body.Bytes()) return + } + + data := rec.Body.String() + + // we serve the directoy page + if strings.HasPrefix(data, "<pre>") { + execTemplate(w, r, data) } else { - t.handler.ServeHTTP(w, r) + w.Write(rec.Body.Bytes()) } } @@ -59,10 +83,9 @@ func execTemplate(w http.ResponseWriter, r *http.Request, data string) { data = strings.Replace(data, "<pre>", "", 1) data = strings.Replace(data, "</pre>", "", 1) data = strings.Replace(data, "</a>", "</a><br>", -1) - data = "<a class=\"dir\" href=\"..\">.. (up a dir)</a><br><br>" + data tmpl := template.New("page") - tmpl, err := tmpl.Parse(renderTemplate()) + tmpl, err := tmpl.Parse(getTemplate()) if err != nil { log.Println(err.Error()) w.WriteHeader(500) @@ -84,73 +107,25 @@ func NewTemplateHandler(handler http.Handler, allowedHost string) *TemplateHandl return &TemplateHandler{handler: handler} } -func renderTemplate() string { - html := `<!doctype html> +func getTemplate() string { + return `<!doctype html> <html> <head> <meta charset="utf-8"> <title>{{.URL}}</title> <meta name="viewport" content="width=device-width, initial-scale=1"> - <style> - /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-moz-box-sizing:content-box;-webkit-box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column,.columns{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column,.columns{margin-left:4%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:2rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media (min-width:550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1EAEDB}a:hover{color:#0FA0CE}.button,button,input[type=button],input[type=reset],input[type=submit]{display:inline-block;height:38px;padding:0 30px;color:#555;text-align:center;font-size:11px;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #bbb;cursor:pointer;box-sizing:border-box}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{color:#333;border-color:#888;outline:0}.button.button-primary,button.button-primary,input[type=button].button-primary,input[type=reset].button-primary,input[type=submit].button-primary{color:#FFF;background-color:#33C3F0;border-color:#33C3F0}.button.button-primary:focus,.button.button-primary:hover,button.button-primary:focus,button.button-primary:hover,input[type=button].button-primary:focus,input[type=button].button-primary:hover,input[type=reset].button-primary:focus,input[type=reset].button-primary:hover,input[type=submit].button-primary:focus,input[type=submit].button-primary:hover{color:#FFF;background-color:#1EAEDB;border-color:#1EAEDB}input[type=email],input[type=text],input[type=tel],input[type=url],input[type=password],input[type=number],input[type=search],select,textarea{height:38px;padding:6px 10px;background-color:#fff;border:1px solid #D1D1D1;border-radius:4px;box-shadow:none;box-sizing:border-box}input[type=email],input[type=text],input[type=tel],input[type=url],input[type=password],input[type=number],input[type=search],textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none}textarea{min-height:65px;padding-top:6px;padding-bottom:6px}input[type=email]:focus,input[type=text]:focus,input[type=tel]:focus,input[type=url]:focus,input[type=password]:focus,input[type=number]:focus,input[type=search]:focus,select:focus,textarea:focus{border:1px solid #33C3F0;outline:0}label,legend{display:block;margin-bottom:.5rem;font-weight:600}fieldset{padding:0;border-width:0}input[type=checkbox],input[type=radio]{display:inline}label>.label-body{display:inline-block;margin-left:.5rem;font-weight:400}ul{list-style:none}ol{list-style:decimal inside}ol,ul{padding-left:0;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin:1.5rem 0 1.5rem 3rem;font-size:90%}li{margin-bottom:1rem}code{padding:.2rem .5rem;margin:0 .2rem;font-size:90%;white-space:nowrap;background:#F1F1F1;border:1px solid #E1E1E1;border-radius:4px}pre>code{display:block;padding:1rem 1.5rem;white-space:pre}td,th{padding:12px 15px;text-align:left;border-bottom:1px solid #E1E1E1}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}.button,button{margin-bottom:1rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #E1E1E1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.margin-top{margin-top:20px}.dir,.dir:hover{color: #000}.dir:hover{text-decoration:none}.dir:before{content: "> "} -div.icon { - height: 32px; - width: 32px; - position: relative; - margin: 15px; - overflow: hidden; - display: inline-block; -} -div.icon div.home { - height: 0px; - width: 0px; - border-width: 16px; - border-style: solid; - border-color: transparent transparent #333 transparent; - position: absolute; - bottom: 16px; - left: 0; -} - -div.icon div.home:after { - content: ''; - width: 5px; - height: 16px; - background-color: transparent; - position: absolute; - top: 16px; - right: -11px; - border-left: 8px solid #333; - border-right: 8px solid #333; -} - -div.icon div.home:before { - content: ''; - width: 9px; - height: 6px; - background-color: #333; - position: absolute; - top: 16px; - right: -5px; -} - -div.icon div.chimney { - width: 4px; - height: 10px; - background: #333; - position: absolute; - right: 6px; - top: 3px; -} - </style> + <style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}a:active,a:hover{outline:0}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column{margin-left:4%}.column:first-child{margin-left:0}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h4{margin-top:0;margin-bottom:2rem;font-weight:300;font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}@media (min-width:550px){h4{font-size:3rem}}a{color:#1EAEDB}a:hover{color:#0FA0CE}blockquote{margin-bottom:2.5rem}.container:after,.row:after{content:"";display:table;clear:both}.margin-top{margin-top:20px}.dir,.dir:hover{color:#000}.dir:hover{text-decoration:none}.dir:before{content:"> "}div.icon{height:32px;width:32px;position:relative;margin:15px;overflow:hidden;display:inline-block}div.icon div.home{height:0;width:0;border-width:16px;border-style:solid;border-color:transparent transparent #333;position:absolute;bottom:16px;left:0}div.icon div.home:after{content:'';width:5px;height:16px;background-color:transparent;position:absolute;top:16px;right:-11px;border-left:8px solid #333;border-right:8px solid #333}div.icon div.home:before{content:'';width:9px;height:6px;background-color:#333;position:absolute;top:16px;right:-5px}div.icon div.chimney{width:4px;height:10px;background:#333;position:absolute;right:6px;top:3px}</style> </head> <body> <div class="container"> <div class="row"> <div class="column margin-top"> - <a href="/.."><div class="icon"><div class="home"></div><div class="chimney"></div></div></a> - <h4>{{.URL}}</h4> + <h4><a href="/.." title="Go back to root"><div class="icon"><div class="home"></div><div class="chimney"></div></div></a><br> + {{.URL}}</h4> <blockquote> + {{if ne .URL "/"}} + <a class="dir" href="..">.. (up a dir)</a><br><br> + {{end}} {{.Data}} </blockquote> </div> @@ -171,6 +146,27 @@ div.icon div.chimney { </script> </body> </html>` +} - return html +func get404() string { + return `<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Not Found - {{.URL}}</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}a:active,a:hover{outline:0}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h3{margin-top:0;margin-bottom:2rem;font-weight:300;font-size:3rem;line-height:1.3;letter-spacing:-.1rem}@media (min-width:550px){h3{font-size:3.6rem}}p{margin-top:0}a{color:#1EAEDB}a:hover{color:#0FA0CE}.button{display:inline-block;height:38px;padding:0 30px;color:#555;text-align:center;font-size:11px;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #bbb;cursor:pointer;box-sizing:border-box}.button:focus,.button:hover{color:#333;border-color:#888;outline:0}.button.button-primary{color:#FFF;background-color:#33C3F0;border-color:#33C3F0}.button.button-primary:focus,.button.button-primary:hover{color:#FFF;background-color:#1EAEDB;border-color:#1EAEDB}.button{margin-bottom:1rem}p{margin-bottom:2.5rem}.container:after{content:"";display:table;clear:both}.section{padding:8rem 0 7rem;text-align:center}.section-description,.section-heading{margin-bottom:1.2rem}</style> +</head> +<body> +<div class="section"> + <div class="container"> + <h3 class="section-heading">Not Found - {{.URL}}</h3> + <p class="section-description"> + Please check for typos in your url. </p> + <p class="section-description"> + <a href="/" class="button button-primary">Go back to root</a></p> + </div> +</div> +</body> +</html>` } |
