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  }