github.com/searKing/golang/go@v1.2.117/net/http/fs.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http 6 7 import ( 8 "bufio" 9 "errors" 10 "fmt" 11 "io" 12 "mime" 13 "net/http" 14 "os" 15 "path/filepath" 16 "time" 17 18 io_ "github.com/searKing/golang/go/io" 19 ) 20 21 // The algorithm uses at most sniffLen bytes to make its decision. 22 const sniffLen = 512 23 24 // ContentType implements the algorithm described 25 // at https://mimesniff.spec.whatwg.org/ to determine the 26 // Content-Type of the given data. It considers at most the 27 // first 512 bytes of data from r. ContentType always returns 28 // a valid MIME type: if it cannot determine a more specific one, it 29 // returns "application/octet-stream". 30 // ContentType is based on http.DetectContentType. 31 func ContentType(r io.Reader, name string) (ctype string, bufferedContent io.Reader, err error) { 32 ctype = mime.TypeByExtension(filepath.Ext(name)) 33 if ctype == "" && r != nil { 34 // read a chunk to decide between utf-8 text and binary 35 var buf [sniffLen]byte 36 var n int 37 if readSeeker, ok := r.(io.Seeker); ok { 38 n, _ = io.ReadFull(r, buf[:]) 39 _, err = readSeeker.Seek(0, io.SeekStart) // rewind to output whole file 40 if err != nil { 41 err = errors.New("seeker can't seek") 42 return "", r, err 43 } 44 } else { 45 contentBuffer := bufio.NewReader(r) 46 sniffed, err := contentBuffer.Peek(sniffLen) 47 if err != nil { 48 err = errors.New("reader can't read") 49 return "", contentBuffer, err 50 } 51 n = copy(buf[:], sniffed) 52 r = contentBuffer 53 } 54 ctype = http.DetectContentType(buf[:n]) 55 } 56 return ctype, r, nil 57 } 58 59 // ServeContent replies to the request using the content in the 60 // provided Reader. The main benefit of ServeContent over io.Copy 61 // is that it handles Range requests properly, sets the MIME type, and 62 // handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, 63 // and If-Range requests. 64 // 65 // If the response's Content-Type header is not set, ServeContent 66 // first tries to deduce the type from name's file extension and, 67 // if that fails, falls back to reading the first block of the content 68 // and passing it to DetectContentType. 69 // The name is otherwise unused; in particular it can be empty and is 70 // never sent in the response. 71 // 72 // If modtime is not the zero time or Unix epoch, ServeContent 73 // includes it in a Last-Modified header in the response. If the 74 // request includes an If-Modified-Since header, ServeContent uses 75 // modtime to decide whether the content needs to be sent at all. 76 // 77 // If the content's Seek method work: ServeContent uses 78 // a seek to the end of the content to determine its size, and the param size is ignored. The same as http.ServeFile 79 // If the content's Seek method doesn't work: ServeContent uses the param size 80 // to generate a onlySizeSeekable as a pseudo io.ReadSeeker. If size < 0, use chunk or connection close instead 81 // 82 // If the caller has set w's ETag header formatted per RFC 7232, section 2.3, 83 // ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range. 84 // 85 // Note that *os.File implements the io.ReadSeeker interface. 86 func ServeContent(w http.ResponseWriter, r *http.Request, name string, modtime time.Time, content io.Reader, size int64) { 87 readseeker, seekable := content.(io.ReadSeeker) 88 89 // generate a onlySizeSeekable as a pseudo io.ReadSeeker 90 if !seekable { 91 92 rangeReq := r.Header.Get("Range") 93 if rangeReq != "" { 94 ranges, err := parseRange(rangeReq, size) 95 if err != nil { 96 if err == errNoOverlap { 97 w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size)) 98 } 99 http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable) 100 return 101 } 102 for _, r := range ranges { 103 if r.start != 0 { 104 // only Range: bytes=0- is supported for none seekable reader 105 http.Error(w, "range is not support", http.StatusRequestedRangeNotSatisfiable) 106 return 107 } 108 } 109 } 110 111 // Content-Type must be set here, avoid sniff in http.ServeContent for onlySizeSeekable later 112 // If Content-Type isn't set, use the file's extension to find it, but 113 // if the Content-Type is unset explicitly, do not sniff the type. 114 ctypes, haveType := w.Header()["Content-Type"] 115 var ctype string 116 if !haveType { 117 var err error 118 ctype, content, err = ContentType(content, name) 119 if err != nil { 120 http.Error(w, err.Error(), http.StatusInternalServerError) 121 return 122 } 123 } else if len(ctypes) > 0 { 124 ctype = ctypes[0] 125 } 126 127 w.Header().Set("Content-Type", ctype) 128 129 if size < 0 { 130 // to reject unsupported Range 131 w.Header().Del("Content-Length") 132 w.Header().Set("Content-Encoding", "chunked") 133 134 // Use HTTP Trunk or connection close later 135 defer func() { 136 if r.Method != "HEAD" { 137 _, _ = io.Copy(w, content) 138 } 139 }() 140 } 141 142 readseeker = newOnlySizeSeekable(content, size) 143 } 144 145 if size >= 0 { 146 readseeker = io_.LimitReadSeeker(readseeker, size) 147 } 148 149 if stater, ok := content.(io_.Stater); ok { 150 if fi, err := stater.Stat(); err == nil { 151 modtime = fi.ModTime() 152 } 153 } 154 http.ServeContent(w, r, name, modtime, readseeker) 155 156 // Use HTTP Trunk or connection close by defer 157 158 return 159 } 160 161 // can only be used for ServeContent 162 type onlySizeSeekable struct { 163 r io.Reader 164 size int64 165 offset int64 166 } 167 168 func newOnlySizeSeekable(r io.Reader, size int64) *onlySizeSeekable { 169 return &onlySizeSeekable{ 170 r: r, 171 size: size, 172 } 173 } 174 175 func (s *onlySizeSeekable) Seek(offset int64, whence int) (int64, error) { 176 if offset != 0 { 177 return 0, os.ErrInvalid 178 } 179 if whence == io.SeekStart { 180 s.offset = 0 181 return s.offset, nil 182 } 183 if whence == io.SeekEnd { 184 s.offset = s.size 185 return s.offset, nil 186 } 187 if whence == io.SeekCurrent { 188 return s.offset, nil 189 } 190 return s.offset, os.ErrInvalid 191 } 192 193 func (s *onlySizeSeekable) Read(p []byte) (n int, err error) { 194 n, err = s.r.Read(p) 195 s.offset += int64(n) 196 return n, err 197 }