github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_request_root.go (about)

     1  package nodes
     2  
     3  import (
     4  	"fmt"
     5  	rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
     6  	"github.com/TeaOSLab/EdgeNode/internal/zero"
     7  	"github.com/cespare/xxhash/v2"
     8  	"github.com/iwind/TeaGo/Tea"
     9  	"github.com/iwind/TeaGo/logs"
    10  	"github.com/iwind/TeaGo/types"
    11  	"io"
    12  	"io/fs"
    13  	"mime"
    14  	"net/http"
    15  	"net/url"
    16  	"os"
    17  	"path/filepath"
    18  	"strconv"
    19  	"strings"
    20  )
    21  
    22  // 文本mime-type列表
    23  var textMimeMap = map[string]zero.Zero{
    24  	"application/atom+xml":                {},
    25  	"application/javascript":              {},
    26  	"application/x-javascript":            {},
    27  	"application/json":                    {},
    28  	"application/rss+xml":                 {},
    29  	"application/x-web-app-manifest+json": {},
    30  	"application/xhtml+xml":               {},
    31  	"application/xml":                     {},
    32  	"image/svg+xml":                       {},
    33  	"text/css":                            {},
    34  	"text/plain":                          {},
    35  	"text/javascript":                     {},
    36  	"text/xml":                            {},
    37  	"text/html":                           {},
    38  	"text/xhtml":                          {},
    39  	"text/sgml":                           {},
    40  }
    41  
    42  // 调用本地静态资源
    43  // 如果返回true,则终止请求
    44  func (this *HTTPRequest) doRoot() (isBreak bool) {
    45  	if this.web.Root == nil || !this.web.Root.IsOn {
    46  		return
    47  	}
    48  
    49  	if len(this.uri) == 0 {
    50  		this.write404()
    51  		return true
    52  	}
    53  
    54  	var rootDir = this.web.Root.Dir
    55  	if this.web.Root.HasVariables() {
    56  		rootDir = this.Format(rootDir)
    57  	}
    58  	if !filepath.IsAbs(rootDir) {
    59  		rootDir = Tea.Root + Tea.DS + rootDir
    60  	}
    61  
    62  	var requestPath = this.uri
    63  
    64  	var questionMarkIndex = strings.Index(this.uri, "?")
    65  	if questionMarkIndex > -1 {
    66  		requestPath = this.uri[:questionMarkIndex]
    67  	}
    68  
    69  	// except hidden files
    70  	if this.web.Root.ExceptHiddenFiles &&
    71  		(strings.Contains(requestPath, "/.") || strings.Contains(requestPath, "\\.")) {
    72  		this.write404()
    73  		return true
    74  	}
    75  
    76  	// except and only files
    77  	if !this.web.Root.MatchURL(this.URL()) {
    78  		this.write404()
    79  		return true
    80  	}
    81  
    82  	// 去掉其中的奇怪的路径
    83  	requestPath = strings.Replace(requestPath, "..\\", "", -1)
    84  
    85  	// 进行URL Decode
    86  	if this.web.Root.DecodePath {
    87  		p, err := url.QueryUnescape(requestPath)
    88  		if err == nil {
    89  			requestPath = p
    90  		} else {
    91  			if !this.canIgnore(err) {
    92  				logs.Error(err)
    93  			}
    94  		}
    95  	}
    96  
    97  	// 去掉前缀
    98  	stripPrefix := this.web.Root.StripPrefix
    99  	if len(stripPrefix) > 0 {
   100  		if stripPrefix[0] != '/' {
   101  			stripPrefix = "/" + stripPrefix
   102  		}
   103  
   104  		requestPath = strings.TrimPrefix(requestPath, stripPrefix)
   105  		if len(requestPath) == 0 || requestPath[0] != '/' {
   106  			requestPath = "/" + requestPath
   107  		}
   108  	}
   109  
   110  	var filename = strings.Replace(requestPath, "/", Tea.DS, -1)
   111  	var filePath string
   112  	if len(filename) > 0 && filename[0:1] == Tea.DS {
   113  		filePath = rootDir + filename
   114  	} else {
   115  		filePath = rootDir + Tea.DS + filename
   116  	}
   117  
   118  	this.filePath = filePath // 用来记录日志
   119  
   120  	stat, err := os.Stat(filePath)
   121  	if err != nil {
   122  		_, isPathError := err.(*fs.PathError)
   123  		if os.IsNotExist(err) || isPathError {
   124  			if this.web.Root.IsBreak {
   125  				this.write404()
   126  				return true
   127  			}
   128  			return
   129  		} else {
   130  			this.write50x(err, http.StatusInternalServerError, "Failed to stat the file", "查看文件统计信息失败", true)
   131  			if !this.canIgnore(err) {
   132  				logs.Error(err)
   133  			}
   134  			return true
   135  		}
   136  	}
   137  	if stat.IsDir() {
   138  		indexFile, indexStat := this.findIndexFile(filePath)
   139  		if len(indexFile) > 0 {
   140  			filePath += Tea.DS + indexFile
   141  		} else {
   142  			if this.web.Root.IsBreak {
   143  				this.write404()
   144  				return true
   145  			}
   146  			return
   147  		}
   148  		this.filePath = filePath
   149  
   150  		// stat again
   151  		if indexStat == nil {
   152  			stat, err = os.Stat(filePath)
   153  			if err != nil {
   154  				if os.IsNotExist(err) {
   155  					if this.web.Root.IsBreak {
   156  						this.write404()
   157  						return true
   158  					}
   159  					return
   160  				} else {
   161  					this.write50x(err, http.StatusInternalServerError, "Failed to stat the file", "查看文件统计信息失败", true)
   162  					if !this.canIgnore(err) {
   163  						logs.Error(err)
   164  					}
   165  					return true
   166  				}
   167  			}
   168  		} else {
   169  			stat = indexStat
   170  		}
   171  	}
   172  
   173  	// 响应header
   174  	var respHeader = this.writer.Header()
   175  
   176  	// mime type
   177  	var contentType = ""
   178  	if this.web.ResponseHeaderPolicy == nil || !this.web.ResponseHeaderPolicy.IsOn || !this.web.ResponseHeaderPolicy.ContainsHeader("CONTENT-TYPE") {
   179  		var ext = filepath.Ext(filePath)
   180  		if len(ext) > 0 {
   181  			mimeType := mime.TypeByExtension(ext)
   182  			if len(mimeType) > 0 {
   183  				var semicolonIndex = strings.Index(mimeType, ";")
   184  				var mimeTypeKey = mimeType
   185  				if semicolonIndex > 0 {
   186  					mimeTypeKey = mimeType[:semicolonIndex]
   187  				}
   188  
   189  				if _, found := textMimeMap[mimeTypeKey]; found {
   190  					if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
   191  						var charset = this.web.Charset.Charset
   192  						if this.web.Charset.IsUpper {
   193  							charset = strings.ToUpper(charset)
   194  						}
   195  						contentType = mimeTypeKey + "; charset=" + charset
   196  						respHeader.Set("Content-Type", mimeTypeKey+"; charset="+charset)
   197  					} else {
   198  						contentType = mimeType
   199  						respHeader.Set("Content-Type", mimeType)
   200  					}
   201  				} else {
   202  					contentType = mimeType
   203  					respHeader.Set("Content-Type", mimeType)
   204  				}
   205  			}
   206  		}
   207  	}
   208  
   209  	// length
   210  	var fileSize = stat.Size()
   211  
   212  	// 支持 Last-Modified
   213  	modifiedTime := stat.ModTime().Format("Mon, 02 Jan 2006 15:04:05 GMT")
   214  	if len(respHeader.Get("Last-Modified")) == 0 {
   215  		respHeader.Set("Last-Modified", modifiedTime)
   216  	}
   217  
   218  	// 支持 ETag
   219  	var eTag = "\"e" + fmt.Sprintf("%0x", xxhash.Sum64String(filename+strconv.FormatInt(stat.ModTime().UnixNano(), 10)+strconv.FormatInt(stat.Size(), 10))) + "\""
   220  	if len(respHeader.Get("ETag")) == 0 {
   221  		respHeader.Set("ETag", eTag)
   222  	}
   223  
   224  	// 调用回调
   225  	this.onRequest()
   226  	if this.writer.isFinished {
   227  		return
   228  	}
   229  
   230  	// 支持 If-None-Match
   231  	if this.requestHeader("If-None-Match") == eTag {
   232  		// 自定义Header
   233  		this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
   234  		this.writer.WriteHeader(http.StatusNotModified)
   235  		return true
   236  	}
   237  
   238  	// 支持 If-Modified-Since
   239  	if this.requestHeader("If-Modified-Since") == modifiedTime {
   240  		// 自定义Header
   241  		this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
   242  		this.writer.WriteHeader(http.StatusNotModified)
   243  		return true
   244  	}
   245  
   246  	// 支持Range
   247  	respHeader.Set("Accept-Ranges", "bytes")
   248  	ifRangeHeaders, ok := this.RawReq.Header["If-Range"]
   249  	var supportRange = true
   250  	if ok {
   251  		supportRange = false
   252  		for _, v := range ifRangeHeaders {
   253  			if v == eTag || v == modifiedTime {
   254  				supportRange = true
   255  				break
   256  			}
   257  		}
   258  		if !supportRange {
   259  			respHeader.Del("Accept-Ranges")
   260  		}
   261  	}
   262  
   263  	// 支持Range
   264  	var ranges = []rangeutils.Range{}
   265  	if supportRange {
   266  		var contentRange = this.RawReq.Header.Get("Range")
   267  		if len(contentRange) > 0 {
   268  			if fileSize == 0 {
   269  				this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   270  				this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   271  				return true
   272  			}
   273  
   274  			set, ok := httpRequestParseRangeHeader(contentRange)
   275  			if !ok {
   276  				this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   277  				this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   278  				return true
   279  			}
   280  			if len(set) > 0 {
   281  				ranges = set
   282  				for k, r := range ranges {
   283  					r2, ok := r.Convert(fileSize)
   284  					if !ok {
   285  						this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   286  						this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   287  						return true
   288  					}
   289  					ranges[k] = r2
   290  				}
   291  			}
   292  		} else {
   293  			respHeader.Set("Content-Length", strconv.FormatInt(fileSize, 10))
   294  		}
   295  	} else {
   296  		respHeader.Set("Content-Length", strconv.FormatInt(fileSize, 10))
   297  	}
   298  
   299  	fileReader, err := os.OpenFile(filePath, os.O_RDONLY, 0444)
   300  	if err != nil {
   301  		this.write50x(err, http.StatusInternalServerError, "Failed to open the file", "试图打开文件失败", true)
   302  		return true
   303  	}
   304  
   305  	// 自定义Header
   306  	this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
   307  
   308  	// 在Range请求中不能缓存
   309  	if len(ranges) > 0 {
   310  		this.cacheRef = nil // 不支持缓存
   311  	}
   312  
   313  	var resp = &http.Response{
   314  		ContentLength: fileSize,
   315  		Body:          fileReader,
   316  		StatusCode:    http.StatusOK,
   317  	}
   318  	this.writer.Prepare(resp, fileSize, http.StatusOK, true)
   319  
   320  	var pool = this.bytePool(fileSize)
   321  	var buf = pool.Get()
   322  	defer func() {
   323  		pool.Put(buf)
   324  	}()
   325  
   326  	if len(ranges) == 1 {
   327  		respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(types.String(fileSize)))
   328  		this.writer.WriteHeader(http.StatusPartialContent)
   329  
   330  		ok, err := httpRequestReadRange(resp.Body, buf.Bytes, ranges[0].Start(), ranges[0].End(), func(buf []byte, n int) error {
   331  			_, err := this.writer.Write(buf[:n])
   332  			return err
   333  		})
   334  		if err != nil {
   335  			if !this.canIgnore(err) {
   336  				logs.Error(err)
   337  			}
   338  			return true
   339  		}
   340  		if !ok {
   341  			this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   342  			this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   343  			return true
   344  		}
   345  	} else if len(ranges) > 1 {
   346  		var boundary = httpRequestGenBoundary()
   347  		respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
   348  
   349  		this.writer.WriteHeader(http.StatusPartialContent)
   350  
   351  		for index, r := range ranges {
   352  			if index == 0 {
   353  				_, err = this.writer.WriteString("--" + boundary + "\r\n")
   354  			} else {
   355  				_, err = this.writer.WriteString("\r\n--" + boundary + "\r\n")
   356  			}
   357  			if err != nil {
   358  				if !this.canIgnore(err) {
   359  					logs.Error(err)
   360  				}
   361  				return true
   362  			}
   363  
   364  			_, err = this.writer.WriteString("Content-Range: " + r.ComposeContentRangeHeader(types.String(fileSize)) + "\r\n")
   365  			if err != nil {
   366  				if !this.canIgnore(err) {
   367  					logs.Error(err)
   368  				}
   369  				return true
   370  			}
   371  
   372  			if len(contentType) > 0 {
   373  				_, err = this.writer.WriteString("Content-Type: " + contentType + "\r\n\r\n")
   374  				if err != nil {
   375  					if !this.canIgnore(err) {
   376  						logs.Error(err)
   377  					}
   378  					return true
   379  				}
   380  			}
   381  
   382  			ok, err := httpRequestReadRange(resp.Body, buf.Bytes, r.Start(), r.End(), func(buf []byte, n int) error {
   383  				_, err := this.writer.Write(buf[:n])
   384  				return err
   385  			})
   386  			if err != nil {
   387  				if !this.canIgnore(err) {
   388  					logs.Error(err)
   389  				}
   390  				return true
   391  			}
   392  			if !ok {
   393  				this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   394  				this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   395  				return true
   396  			}
   397  		}
   398  
   399  		_, err = this.writer.WriteString("\r\n--" + boundary + "--\r\n")
   400  		if err != nil {
   401  			if !this.canIgnore(err) {
   402  				logs.Error(err)
   403  			}
   404  			return true
   405  		}
   406  	} else {
   407  		_, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
   408  		if err != nil {
   409  			if !this.canIgnore(err) {
   410  				logs.Error(err)
   411  			}
   412  			return true
   413  		}
   414  	}
   415  
   416  	// 设置成功
   417  	this.writer.SetOk()
   418  
   419  	return true
   420  }
   421  
   422  // 查找首页文件
   423  func (this *HTTPRequest) findIndexFile(dir string) (filename string, stat os.FileInfo) {
   424  	if this.web.Root == nil || !this.web.Root.IsOn {
   425  		return "", nil
   426  	}
   427  	if len(this.web.Root.Indexes) == 0 {
   428  		return "", nil
   429  	}
   430  	for _, index := range this.web.Root.Indexes {
   431  		if len(index) == 0 {
   432  			continue
   433  		}
   434  
   435  		// 模糊查找
   436  		if strings.Contains(index, "*") {
   437  			indexFiles, err := filepath.Glob(dir + Tea.DS + index)
   438  			if err != nil {
   439  				if !this.canIgnore(err) {
   440  					logs.Error(err)
   441  				}
   442  				this.addError(err)
   443  				continue
   444  			}
   445  			if len(indexFiles) > 0 {
   446  				return filepath.Base(indexFiles[0]), nil
   447  			}
   448  			continue
   449  		}
   450  
   451  		// 精确查找
   452  		filePath := dir + Tea.DS + index
   453  		stat, err := os.Stat(filePath)
   454  		if err != nil || !stat.Mode().IsRegular() {
   455  			continue
   456  		}
   457  		return index, stat
   458  	}
   459  	return "", nil
   460  }