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 }