Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
file system server add NotFound, Redirect functions method on a string R=r DELTA=212 (199 added, 4 deleted, 9 changed) OCL=27467 CL=27471
- Loading branch information
Showing
3 changed files
with
209 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright 2009 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// HTTP file system request handler | ||
|
||
package http | ||
|
||
import ( | ||
"fmt"; | ||
"http"; | ||
"io"; | ||
"os"; | ||
"path"; | ||
"strings"; | ||
"utf8"; | ||
) | ||
|
||
// TODO this should be in a mime package somewhere | ||
var contentByExt = map[string] string { | ||
".css": "text/css", | ||
".gif": "image/gif", | ||
".html": "text/html; charset=utf-8", | ||
".jpg": "image/jpeg", | ||
".js": "application/x-javascript", | ||
".png": "image/png", | ||
} | ||
|
||
// Heuristic: b is text if it is valid UTF-8 and doesn't | ||
// contain any unprintable ASCII or Unicode characters. | ||
func isText(b []byte) bool { | ||
for len(b) > 0 && utf8.FullRune(b) { | ||
rune, size := utf8.DecodeRune(b); | ||
if size == 1 && rune == utf8.RuneError { | ||
// decoding error | ||
return false; | ||
} | ||
if 0x80 <= rune && rune <= 0x9F { | ||
return false; | ||
} | ||
if rune < ' ' { | ||
switch rune { | ||
case '\n', '\r', '\t': | ||
// okay | ||
default: | ||
// binary garbage | ||
return false; | ||
} | ||
} | ||
b = b[size:len(b)]; | ||
} | ||
return true; | ||
} | ||
|
||
func dirList(c *Conn, f *os.File) { | ||
fmt.Fprintf(c, "<pre>\n"); | ||
for { | ||
dirs, err := f.Readdir(100); | ||
if err != nil || len(dirs) == 0 { | ||
break | ||
} | ||
for i, d := range dirs { | ||
name := d.Name; | ||
if d.IsDirectory() { | ||
name += "/" | ||
} | ||
// TODO htmlescape | ||
fmt.Fprintf(c, "<a href=\"%s\">%s</a>\n", name, name); | ||
} | ||
} | ||
fmt.Fprintf(c, "</pre>\n"); | ||
} | ||
|
||
|
||
func serveFileInternal(c *Conn, r *Request, name string, redirect bool) { | ||
const indexPage = "/index.html"; | ||
|
||
// redirect to strip off any index.html | ||
n := len(name) - len(indexPage); | ||
if n >= 0 && name[n:len(name)] == indexPage { | ||
http.Redirect(c, name[0:n+1]); | ||
return; | ||
} | ||
|
||
f, err := os.Open(name, os.O_RDONLY, 0); | ||
if err != nil { | ||
// TODO expose actual error? | ||
NotFound(c, r); | ||
return; | ||
} | ||
defer f.Close(); | ||
|
||
d, err1 := f.Stat(); | ||
if err1 != nil { | ||
// TODO expose actual error? | ||
NotFound(c, r); | ||
return; | ||
} | ||
|
||
if redirect { | ||
// redirect to canonical path: / at end of directory url | ||
// r.Url.Path always begins with / | ||
url := r.Url.Path; | ||
if d.IsDirectory() { | ||
if url[len(url)-1] != '/' { | ||
http.Redirect(c, url + "/"); | ||
return; | ||
} | ||
} else { | ||
if url[len(url)-1] == '/' { | ||
http.Redirect(c, url[0:len(url)-1]); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
// use contents of index.html for directory, if present | ||
if d.IsDirectory() { | ||
index := name + indexPage; | ||
ff, err := os.Open(index, os.O_RDONLY, 0); | ||
if err == nil { | ||
defer ff.Close(); | ||
dd, err := ff.Stat(); | ||
if err == nil { | ||
name = index; | ||
d = dd; | ||
f = ff; | ||
} | ||
} | ||
} | ||
|
||
if d.IsDirectory() { | ||
dirList(c, f); | ||
return; | ||
} | ||
|
||
// serve file | ||
// use extension to find content type. | ||
ext := path.Ext(name); | ||
if ctype, ok := contentByExt[ext]; ok { | ||
c.SetHeader("Content-Type", ctype); | ||
} else { | ||
// read first chunk to decide between utf-8 text and binary | ||
var buf [1024]byte; | ||
n, err := io.Readn(f, buf); | ||
b := buf[0:n]; | ||
if isText(b) { | ||
c.SetHeader("Content-Type", "text-plain; charset=utf-8"); | ||
} else { | ||
c.SetHeader("Content-Type", "application/octet-stream"); // generic binary | ||
} | ||
c.Write(b); | ||
} | ||
io.Copy(f, c); | ||
} | ||
|
||
// ServeFile replies to the request with the contents of the named file or directory. | ||
func ServeFile(c *Conn, r *Request, name string) { | ||
serveFileInternal(c, r, name, false); | ||
} | ||
|
||
type fileHandler struct { | ||
root string; | ||
prefix string; | ||
} | ||
|
||
// FileServer returns a handler that serves HTTP requests | ||
// with the contents of the file system rooted at root. | ||
// It strips prefix from the incoming requests before | ||
// looking up the file name in the file system. | ||
func FileServer(root, prefix string) Handler { | ||
return &fileHandler{root, prefix}; | ||
} | ||
|
||
func (f *fileHandler) ServeHTTP(c *Conn, r *Request) { | ||
path := r.Url.Path; | ||
if !strings.HasPrefix(path, f.prefix) { | ||
NotFound(c, r); | ||
return; | ||
} | ||
path = path[len(f.prefix):len(path)]; | ||
serveFileInternal(c, r, f.root + "/" + path, true); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters