mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2026-02-12 14:05:37 +08:00
实现Web静态文件分发
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -70,7 +71,7 @@ func (this *HTTPRequest) Do() {
|
||||
// 配置
|
||||
err := this.configureWeb(this.Server.Web, true, 0)
|
||||
if err != nil {
|
||||
this.writeInternalServerError()
|
||||
this.write500()
|
||||
this.doEnd()
|
||||
return
|
||||
}
|
||||
@@ -106,8 +107,26 @@ func (this *HTTPRequest) doBegin() {
|
||||
return
|
||||
}
|
||||
|
||||
// Origin
|
||||
// root
|
||||
// TODO 从本地文件中读取
|
||||
// TODO 增加stripPrefix
|
||||
// TODO 增加URLEncode的处理方式
|
||||
// TODO ROOT支持变量
|
||||
if this.web.Root != nil && this.web.Root.IsOn {
|
||||
// 如果处理成功,则终止请求的处理
|
||||
if this.doRoot() {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果明确设置了终止,则也会自动终止
|
||||
if this.web.Root.IsBreak {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse
|
||||
// TODO
|
||||
logs.Println("reverse proxy")
|
||||
|
||||
// WebSocket
|
||||
// TODO
|
||||
@@ -118,14 +137,8 @@ func (this *HTTPRequest) doBegin() {
|
||||
// Server Event Sent
|
||||
// TODO 实现Location的AutoFlush
|
||||
|
||||
// root
|
||||
// TODO 从本地文件中读取
|
||||
// TODO 增加root优先级:High:优先从Root读取,Low:优先从反向代理等条件中读取
|
||||
// TODO 增加stripPrefix
|
||||
// TODO 增加URLEncode的处理方式
|
||||
|
||||
// 返回404页面
|
||||
this.writeNotFoundError()
|
||||
this.write404()
|
||||
}
|
||||
|
||||
// 结束调用
|
||||
@@ -187,6 +200,16 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
|
||||
this.web.ResponseHeaderPolicy = web.ResponseHeaderPolicy
|
||||
}
|
||||
|
||||
// root
|
||||
if web.Root != nil && (web.Root.IsPrior || isTop) {
|
||||
this.web.Root = web.Root
|
||||
}
|
||||
|
||||
// charset
|
||||
if web.Charset != nil && (web.Charset.IsPrior || isTop) {
|
||||
this.web.Charset = web.Charset
|
||||
}
|
||||
|
||||
// locations
|
||||
if len(web.LocationRefs) > 0 {
|
||||
var resultLocation *serverconfigs.HTTPLocationConfig
|
||||
@@ -270,8 +293,8 @@ func (this *HTTPRequest) Format(source string) string {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(this.web.Root) > 0 {
|
||||
return filepath.Clean(this.web.Root + this.requestPath())
|
||||
if this.web.Root != nil && this.web.Root.IsOn {
|
||||
return filepath.Clean(this.web.Root.Dir + this.requestPath())
|
||||
}
|
||||
|
||||
return ""
|
||||
@@ -322,7 +345,10 @@ func (this *HTTPRequest) Format(source string) string {
|
||||
case "hostname":
|
||||
return HOSTNAME
|
||||
case "documentRoot":
|
||||
return this.web.Root
|
||||
if this.web.Root != nil {
|
||||
return this.web.Root.Dir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
dotIndex := strings.Index(varName, ".")
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (this *HTTPRequest) writeNotFoundError() {
|
||||
func (this *HTTPRequest) write404() {
|
||||
if this.doPage(http.StatusNotFound) {
|
||||
return
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func (this *HTTPRequest) writeNotFoundError() {
|
||||
_, _ = this.writer.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func (this *HTTPRequest) writeInternalServerError() {
|
||||
func (this *HTTPRequest) write500() {
|
||||
statusCode := http.StatusInternalServerError
|
||||
if this.doPage(statusCode) {
|
||||
return
|
||||
|
||||
278
internal/nodes/http_request_root.go
Normal file
278
internal/nodes/http_request_root.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dchest/siphash"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 文本mime-type列表
|
||||
var textMimeMap = map[string]bool{
|
||||
"application/atom+xml": true,
|
||||
"application/javascript": true,
|
||||
"application/x-javascript": true,
|
||||
"application/json": true,
|
||||
"application/rss+xml": true,
|
||||
"application/x-web-app-manifest+json": true,
|
||||
"application/xhtml+xml": true,
|
||||
"application/xml": true,
|
||||
"image/svg+xml": true,
|
||||
"text/css": true,
|
||||
"text/plain": true,
|
||||
"text/javascript": true,
|
||||
"text/xml": true,
|
||||
"text/html": true,
|
||||
"text/xhtml": true,
|
||||
"text/sgml": true,
|
||||
}
|
||||
|
||||
// 调用本地静态资源
|
||||
// 如果返回true,则终止请求
|
||||
func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
if this.web.Root == nil || !this.web.Root.IsOn {
|
||||
return
|
||||
}
|
||||
|
||||
if len(this.uri) == 0 {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
|
||||
rootDir := this.web.Root.Dir
|
||||
if !filepath.IsAbs(rootDir) {
|
||||
rootDir = Tea.Root + Tea.DS + rootDir
|
||||
}
|
||||
|
||||
requestPath := this.uri
|
||||
|
||||
questionMarkIndex := strings.Index(this.uri, "?")
|
||||
if questionMarkIndex > -1 {
|
||||
requestPath = this.uri[:questionMarkIndex]
|
||||
}
|
||||
|
||||
// 去掉其中的奇怪的路径
|
||||
requestPath = strings.Replace(requestPath, "..\\", "", -1)
|
||||
|
||||
// 进行URL Decode
|
||||
if this.web.Root.DecodePath {
|
||||
p, err := url.QueryUnescape(requestPath)
|
||||
if err == nil {
|
||||
requestPath = p
|
||||
} else {
|
||||
logs.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 去掉前缀
|
||||
stripPrefix := this.web.Root.StripPrefix
|
||||
if len(stripPrefix) > 0 {
|
||||
if stripPrefix[0] != '/' {
|
||||
stripPrefix = "/" + stripPrefix
|
||||
}
|
||||
|
||||
requestPath = strings.TrimPrefix(requestPath, stripPrefix)
|
||||
if len(requestPath) == 0 || requestPath[0] != '/' {
|
||||
requestPath = "/" + requestPath
|
||||
}
|
||||
}
|
||||
|
||||
filename := strings.Replace(requestPath, "/", Tea.DS, -1)
|
||||
filePath := ""
|
||||
if len(filename) > 0 && filename[0:1] == Tea.DS {
|
||||
filePath = rootDir + filename
|
||||
} else {
|
||||
filePath = rootDir + Tea.DS + filename
|
||||
}
|
||||
|
||||
this.filePath = filePath // 用来记录日志
|
||||
|
||||
stat, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if this.web.Root.IsBreak {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
return
|
||||
} else {
|
||||
this.write500()
|
||||
logs.Error(err)
|
||||
this.addError(err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
if stat.IsDir() {
|
||||
indexFile, indexStat := this.findIndexFile(filePath)
|
||||
if len(indexFile) > 0 {
|
||||
filePath += Tea.DS + indexFile
|
||||
} else {
|
||||
if this.web.Root.IsBreak {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
this.filePath = filePath
|
||||
|
||||
// stat again
|
||||
if indexStat == nil {
|
||||
stat, err = os.Stat(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if this.web.Root.IsBreak {
|
||||
this.write404()
|
||||
return true
|
||||
}
|
||||
return
|
||||
} else {
|
||||
this.write500()
|
||||
logs.Error(err)
|
||||
this.addError(err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stat = indexStat
|
||||
}
|
||||
}
|
||||
|
||||
// 响应header
|
||||
respHeader := this.writer.Header()
|
||||
|
||||
// mime type
|
||||
if !(this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn && this.web.ResponseHeaderPolicy.ContainsHeader("CONTENT-TYPE")) {
|
||||
ext := filepath.Ext(requestPath)
|
||||
if len(ext) > 0 {
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if len(mimeType) > 0 {
|
||||
if _, found := textMimeMap[mimeType]; found {
|
||||
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
|
||||
charset := this.web.Charset.Charset
|
||||
|
||||
// 去掉里面的charset设置
|
||||
index := strings.Index(mimeType, "charset=")
|
||||
if index > 0 {
|
||||
respHeader.Set("Content-Type", mimeType[:index+len("charset=")]+charset)
|
||||
} else {
|
||||
respHeader.Set("Content-Type", mimeType+"; charset="+charset)
|
||||
}
|
||||
} else {
|
||||
respHeader.Set("Content-Type", mimeType)
|
||||
}
|
||||
} else {
|
||||
respHeader.Set("Content-Type", mimeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// length
|
||||
fileSize := stat.Size()
|
||||
respHeader.Set("Content-Length", strconv.FormatInt(fileSize, 10))
|
||||
|
||||
// 支持 Last-Modified
|
||||
modifiedTime := stat.ModTime().Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
if len(respHeader.Get("Last-Modified")) == 0 {
|
||||
respHeader.Set("Last-Modified", modifiedTime)
|
||||
}
|
||||
|
||||
// 支持 ETag
|
||||
eTag := "\"et" + fmt.Sprintf("%0x", siphash.Hash(0, 0, []byte(filename+strconv.FormatInt(stat.ModTime().UnixNano(), 10)+strconv.FormatInt(stat.Size(), 10)))) + "\""
|
||||
if len(respHeader.Get("ETag")) == 0 {
|
||||
respHeader.Set("ETag", eTag)
|
||||
}
|
||||
|
||||
// proxy callback
|
||||
// TODO
|
||||
|
||||
// 支持 If-None-Match
|
||||
if this.requestHeader("If-None-Match") == eTag {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(http.StatusNotModified)
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
|
||||
// 支持 If-Modified-Since
|
||||
if this.requestHeader("If-Modified-Since") == modifiedTime {
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(http.StatusNotModified)
|
||||
this.writer.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
|
||||
// 自定义Header
|
||||
this.processResponseHeaders(http.StatusOK)
|
||||
|
||||
reader, err := os.OpenFile(filePath, os.O_RDONLY, 0444)
|
||||
if err != nil {
|
||||
this.write500()
|
||||
logs.Error(err)
|
||||
this.addError(err)
|
||||
return true
|
||||
}
|
||||
|
||||
this.writer.Prepare(fileSize)
|
||||
|
||||
pool := this.bytePool(fileSize)
|
||||
buf := pool.Get()
|
||||
_, err = io.CopyBuffer(this.writer, reader, buf)
|
||||
pool.Put(buf)
|
||||
|
||||
// 不使用defer,以便于加快速度
|
||||
_ = reader.Close()
|
||||
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 查找首页文件
|
||||
func (this *HTTPRequest) findIndexFile(dir string) (filename string, stat os.FileInfo) {
|
||||
if this.web.Root == nil || !this.web.Root.IsOn {
|
||||
return "", nil
|
||||
}
|
||||
if len(this.web.Root.Indexes) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
for _, index := range this.web.Root.Indexes {
|
||||
if len(index) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 模糊查找
|
||||
if strings.Contains(index, "*") {
|
||||
indexFiles, err := filepath.Glob(dir + Tea.DS + index)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
this.addError(err)
|
||||
continue
|
||||
}
|
||||
if len(indexFiles) > 0 {
|
||||
return filepath.Base(indexFiles[0]), nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 精确查找
|
||||
filePath := dir + Tea.DS + index
|
||||
stat, err := os.Stat(filePath)
|
||||
if err != nil || !stat.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
return index, stat
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
|
||||
if err != nil {
|
||||
logs.Error(errors.New(req.URL.String() + ": " + err.Error()))
|
||||
this.addError(err)
|
||||
this.writeInternalServerError()
|
||||
this.write500()
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
|
||||
Reference in New Issue
Block a user