diff --git a/build/configs/.gitignore b/build/configs/.gitignore index 11e11e6..bd58da1 100644 --- a/build/configs/.gitignore +++ b/build/configs/.gitignore @@ -1,2 +1,2 @@ -node.yaml +node.json api.yaml \ No newline at end of file diff --git a/build/configs/node.json b/build/configs/node.json deleted file mode 100755 index 2b07506..0000000 --- a/build/configs/node.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"db4ab72647d3d2b9c8d9b57b4f4bf47b","isOn":true,"servers":[{"id":3,"type":"httpProxy","isOn":true,"name":"我的服务","description":"","serverNames":[],"http":{"isOn":true,"listen":[{"protocol":"http","host":"127.0.0.1","portRange":"9991"}]},"https":null,"tcp":null,"tls":null,"unix":null,"udp":null,"web":{"id":35,"isOn":true,"locations":[{"id":7,"isOn":true,"pattern":"/hello","name":"","web":{"id":36,"isOn":true,"locations":null,"locationRefs":null,"gzipRef":null,"gzip":null,"charset":null,"shutdown":{"isPrior":false,"isOn":false,"url":"","status":0},"pages":null,"redirectToHttps":{"isPrior":false,"isOn":false,"status":307,"host":"www","port":11111},"root":"","indexes":null,"maxRequestBodySize":"","accessLog":null,"statRef":null,"cacheRef":null,"firewallRef":null,"websocketRef":null,"websocket":null,"requestHeaderPolicyRef":{"isPrior":false,"isOn":true,"headerPolicyId":23},"requestHeaderPolicy":{"id":23,"name":"","isOn":true,"description":"","addHeaderRefs":null,"addHeaders":null,"addTrailerRefs":null,"addTrailers":null,"setHeaderRefs":null,"setHeaders":null,"replaceHeaderRefs":null,"replaceHeaders":null,"deleteHeaders":["Cache-Control","Pragma"],"expires":null},"responseHeaderPolicyRef":{"isPrior":false,"isOn":true,"headerPolicyId":24},"responseHeaderPolicy":{"id":24,"name":"","isOn":true,"description":"","addHeaderRefs":null,"addHeaders":null,"addTrailerRefs":null,"addTrailers":null,"setHeaderRefs":null,"setHeaders":null,"replaceHeaderRefs":null,"replaceHeaders":null,"deleteHeaders":null,"expires":null},"filterRefs":null,"filterPolicies":null},"urlPrefix":"","description":"","reverseProxyRef":null,"reverseProxy":null,"isBreak":false,"children":null,"condGroups":null}],"locationRefs":[{"isOn":true,"locationId":7,"children":null}],"gzipRef":null,"gzip":null,"charset":null,"shutdown":{"isPrior":false,"isOn":false,"url":"hello.html","status":0},"pages":[{"id":14,"isOn":true,"status":["404"],"url":"pages/404.html","newStatus":0}],"redirectToHttps":{"isPrior":false,"isOn":false,"status":307,"host":"","port":0},"root":"","indexes":null,"maxRequestBodySize":"","accessLog":null,"statRef":null,"cacheRef":null,"firewallRef":null,"websocketRef":null,"websocket":null,"requestHeaderPolicyRef":{"isPrior":false,"isOn":true,"headerPolicyId":21},"requestHeaderPolicy":{"id":21,"name":"","isOn":true,"description":"","addHeaderRefs":null,"addHeaders":null,"addTrailerRefs":null,"addTrailers":null,"setHeaderRefs":[{"isOn":true,"headerId":30}],"setHeaders":[{"id":30,"isOn":true,"name":"From","value":"Edge","status":{"always":true,"codes":null}}],"replaceHeaderRefs":null,"replaceHeaders":null,"deleteHeaders":["Cache-Control","Cookie"],"expires":null},"responseHeaderPolicyRef":{"isPrior":false,"isOn":true,"headerPolicyId":22},"responseHeaderPolicy":{"id":22,"name":"","isOn":true,"description":"","addHeaderRefs":null,"addHeaders":null,"addTrailerRefs":null,"addTrailers":null,"setHeaderRefs":[{"isOn":true,"headerId":28},{"isOn":true,"headerId":29}],"setHeaders":[{"id":28,"isOn":true,"name":"Server","value":"Edge","status":{"always":true,"codes":null}},{"id":29,"isOn":true,"name":"Hello","value":"World","status":{"always":true,"codes":null}}],"replaceHeaderRefs":null,"replaceHeaders":null,"deleteHeaders":["Name"],"expires":null},"filterRefs":null,"filterPolicies":null},"reverseProxyRef":{"isPrior":false,"isOn":true,"reverseProxyId":20},"reverseProxy":{"id":20,"isOn":false,"primaryOrigins":null,"primaryOriginRefs":null,"backupOrigins":null,"backupOriginRefs":null,"scheduling":null}}],"version":114,"name":"认证啊","globalConfig":null} \ No newline at end of file diff --git a/go.mod b/go.mod index 7084452..2e190aa 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,11 @@ replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon require ( github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 + github.com/dchest/siphash v1.2.1 github.com/go-ole/go-ole v1.2.4 // indirect - github.com/go-redis/redis v6.15.8+incompatible // indirect github.com/go-yaml/yaml v2.1.0+incompatible github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect github.com/shirou/gopsutil v2.20.7+incompatible - github.com/stretchr/testify v1.6.1 // indirect golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 google.golang.org/grpc v1.32.0 ) diff --git a/go.sum b/go.sum index 70fc1a8..ae9144b 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= +github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -24,7 +26,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= @@ -51,17 +52,10 @@ github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iwind/TeaGo v0.0.0-20200727075925-7e7e67b44f2d h1:V7HA0wUOdmZbXJTVpiUEvSD4ARKHwMLMmiCccfkqf24= -github.com/iwind/TeaGo v0.0.0-20200727075925-7e7e67b44f2d/go.mod h1:zjM7k+b+Jthhf0T0fKwuF0iy4TWb5SsU1gmKR2l+OmE= -github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a h1:VaWcMNOzHHT1y8MeTA2fWhG6GEfAdy6CwF2tW+KiY5Y= -github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= -github.com/iwind/TeaGo v0.0.0-20200910072805-729cffe36729 h1:/v0WhSFVeNay/dA5zU9iCBXlgVDfxnztuanlauXE0gM= -github.com/iwind/TeaGo v0.0.0-20200910072805-729cffe36729/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e h1:/xn7wUvlwaoA5IkdBUctv2OQbJSZ0/Dw8qRJmn55sJk= github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -85,12 +79,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= -github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w= github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -101,7 +91,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= diff --git a/internal/nodes/http_request.go b/internal/nodes/http_request.go index 5c28cd7..0246105 100644 --- a/internal/nodes/http_request.go +++ b/internal/nodes/http_request.go @@ -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, ".") diff --git a/internal/nodes/http_request_error.go b/internal/nodes/http_request_error.go index ded831c..2a98c39 100644 --- a/internal/nodes/http_request_error.go +++ b/internal/nodes/http_request_error.go @@ -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 diff --git a/internal/nodes/http_request_root.go b/internal/nodes/http_request_root.go new file mode 100644 index 0000000..9fbcb5c --- /dev/null +++ b/internal/nodes/http_request_root.go @@ -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 +} diff --git a/internal/nodes/http_request_url.go b/internal/nodes/http_request_url.go index 5680a1c..7e44ad2 100644 --- a/internal/nodes/http_request_url.go +++ b/internal/nodes/http_request_url.go @@ -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() {