From d096eab34bc8d6d6b020e2b34a26f2ab170cb905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E7=BA=A2=E5=B2=A9?= Date: Fri, 25 Aug 2023 18:15:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=86=85=E7=BD=AE=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E4=BB=A3=E7=90=86=20=E6=94=AF=E6=8C=81HttpServer?= =?UTF-8?q?=E5=92=8C=E6=9C=AC=E5=9C=B0=E4=B8=8E=E5=86=85=E7=BD=AE=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E5=8A=A0=E8=BD=BD=E8=B5=84=E6=BA=90.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cef/local-load-resource.go | 118 +++++++++++++---------- cef/local-load-xhr-proxy.go | 167 +++++++++++++++++++++++++++++++++ cef/types-post-data-element.go | 7 ++ cef/types-post-data.go | 7 ++ cef/types-string-multi-map.go | 7 ++ cef/types.go | 3 + consts/consts.go | 20 ++++ 7 files changed, 277 insertions(+), 52 deletions(-) create mode 100644 cef/local-load-xhr-proxy.go diff --git a/cef/local-load-resource.go b/cef/local-load-resource.go index 335c61e..756d328 100644 --- a/cef/local-load-resource.go +++ b/cef/local-load-resource.go @@ -12,12 +12,13 @@ package cef import ( "embed" - "github.com/energye/energy/v2/consts" + . "github.com/energye/energy/v2/consts" "github.com/energye/energy/v2/logger" "io/ioutil" "net/url" "os" "path/filepath" + "strings" "unsafe" ) @@ -25,15 +26,6 @@ const ( localDomain = "energy" // default energy ) -// LocalCustomerScheme 本地资源加载自定义固定协议 -// file, fs -type LocalCustomerScheme string - -const ( - LocalCSFile LocalCustomerScheme = "file" // 本地目录 file://energy/index.html - LocalCSFS LocalCustomerScheme = "fs" // 内置 fs://energy/index.html -) - // 本地加载资源 var localLoadRes *LocalLoadResource @@ -63,27 +55,22 @@ type LocalLoadConfig struct { Scheme LocalCustomerScheme // 自定义协议, file: 本地磁盘目录加载, fs: 内置到执行程序加载 FileRoot string // 资源根目录, scheme是file时为本地目录(默认当前程序执行目录) scheme是fs时为资源文件夹名 FS *embed.FS // 内置加载资源对象, scheme是fs时必须填入 - Proxy XHRProxy // 数据请求代理, 在浏览器发送xhr请求时可通过该配置转发 -} - -// XHRProxy -// 数据请求代理 -type XHRProxy struct { - IP string - Port int + Proxy IXHRProxy // 数据请求代理, 在浏览器发送xhr请求时可通过该配置转发, 你可可以实现该 IXHRProxy 接口自己实现 + Home string // 默认首页: /index.html, /home.html, /other.html, default: /index.html } // 请求和响应资源 type source struct { - path string // 资源路径, 根据请求URL地址 - fileExt string // 资源扩展名, 用于拿到 MimeType - bytes []byte // 资源数据 - err error // 读取资源的错误 - readPosition int // 读取资源时的地址偏移 - status int32 // 响应状态码 - statusText string // 响应状态文本 - mimeType string // 响应的资源 MimeType - resourceType consts.TCefResourceType // 资源类型 + path string // 资源路径, 根据请求URL地址 + fileExt string // 资源扩展名, 用于拿到 MimeType + bytes []byte // 资源数据 + err error // 获取资源时的错误 + readPosition int // 读取资源时的地址偏移 + status int32 // 响应状态码 + statusText string // 响应状态文本 + mimeType string // 响应的资源 MimeType + header map[string][]string // 响应头 + resourceType TCefResourceType // 资源类型 } // 初始化本地加载配置对象 @@ -103,6 +90,18 @@ func localLoadResourceInit(config LocalLoadConfig) { if config.Scheme != LocalCSFile && config.Scheme != LocalCSFS { config.Scheme = LocalCSFS } + if config.Home == "" { + config.Home = "/index.html" + } else if config.Home[0] != '/' { + config.Home = "/" + config.Home + } + //if config.Proxy != nil { + // if proxy, ok := config.Proxy.(*XHRProxy); ok { + // if proxy.Scheme == LpsTcp { + // proxy.tcpListen() + // } + // } + //} localLoadRes.LocalLoadConfig = config } @@ -138,12 +137,13 @@ func (m *LocalLoadResource) ext(path string) string { // 使用本地资源加载时,先验证每个请求的合法性 // 所支持的scheme, domain // URL格式, fs://energy/index.html, 文件路径必须包含扩展名 +// 这里返回false后不会创建资源处理对象 func (m *LocalLoadResource) checkRequest(request *ICefRequest) (*source, bool) { rt := request.ResourceType() // 根据资源类型跳过哪些资源不被本地加载 // TODO: rt_media 类型应该在此去除 switch rt { - case consts.RT_XHR, consts.RT_MEDIA, consts.RT_PING, consts.RT_CSP_REPORT, consts.RT_PLUGIN_RESOURCE: + case RT_MEDIA, RT_PING, RT_CSP_REPORT, RT_PLUGIN_RESOURCE: return nil, false } reqUrl, err := url.Parse(request.URL()) @@ -161,8 +161,11 @@ func (m *LocalLoadResource) checkRequest(request *ICefRequest) (*source, bool) { return nil, false } path := reqUrl.Path + if path == "" || path == "/" { + path = m.Home + } ext := m.ext(path) - if ext == "" { + if ext == "" && rt != RT_XHR { logger.Error("LocalLoadResource Incorrect resources should: file.[ext](MimeType)") return nil, false } @@ -179,70 +182,81 @@ func (m *LocalLoadResource) checkRequest(request *ICefRequest) (*source, bool) { return &source{path: path, fileExt: ext, mimeType: m.getMimeType(ext), resourceType: rt}, true } -// 读取文件 +// 读取本地或内置资源 func (m *source) readFile() bool { // 必须设置文件根目录, scheme是file时, fileRoot为本地文件目录, scheme是fs时, fileRoot为fs的目录名 if localLoadRes.FileRoot != "" { if localLoadRes.Scheme == LocalCSFile { + m.bytes, m.err = ioutil.ReadFile(filepath.Join(localLoadRes.FileRoot, m.path)) // 在本地读取 - data, err := ioutil.ReadFile(filepath.Join(localLoadRes.FileRoot, m.path)) - if err == nil { - m.bytes = data + if m.err == nil { return true } - logger.Error("ReadFile:", err.Error()) + logger.Error("ReadFile:", m.err.Error()) } else if localLoadRes.Scheme == LocalCSFS && localLoadRes.FS != nil { //在fs读取 - data, err := localLoadRes.FS.ReadFile(localLoadRes.FileRoot + m.path) - if err == nil { - m.bytes = data + m.bytes, m.err = localLoadRes.FS.ReadFile(localLoadRes.FileRoot + m.path) + if m.err == nil { return true } - logger.Error("ReadFile:", err.Error()) + logger.Error("ReadFile:", m.err.Error()) } } //失败时,返回404,文件不存在 return false } -// 打开资源 +// checkRequest = true, 打开资源 func (m *source) open(request *ICefRequest, callback *ICefCallback) (handleRequest, result bool) { m.readPosition = 0 // 当前资源的响应设置默认值 m.status = 404 m.statusText = "Not Found" - // 如果开启缓存,直接在缓存取 - if localLoadRes.EnableCache { - if m.bytes == nil { - if !m.readFile() { - return true, true - } + m.err = nil + m.header = nil + // xhr 请求, 需要通过代理转发出去 + if m.resourceType == RT_XHR && localLoadRes.Proxy != nil { + if result, err := localLoadRes.Proxy.Send(request); err == nil { + m.bytes, m.err = result.Data, err + m.status = result.StatusCode + m.header = result.Header + return true, true } } else { - if !m.readFile() { - return true, true + // 如果开启缓存,直接在缓存取 + if localLoadRes.EnableCache { + if m.bytes == nil { + m.readFile() + } + } else { + m.readFile() } } - if m.resourceType == consts.RT_MEDIA { - m.status = 206 - } else { + if m.err == nil { m.status = 200 m.statusText = "OK" + } else { + m.statusText = m.err.Error() } callback.Cont() return true, true } -// 设置响应信息 +// checkRequest = true, 设置响应信息 func (m *source) response(response *ICefResponse) (responseLength int64, redirectUrl string) { response.SetStatus(m.status) response.SetStatusText(m.statusText) response.SetMimeType(m.mimeType) responseLength = int64(len(m.bytes)) + if m.header != nil { + for key, value := range m.header { + response.SetHeaderByName(key, strings.Join(value, ","), true) + } + } return } -// 读取bytes, 返回到dataOut +// checkRequest = true, 读取bytes, 返回到dataOut func (m *source) read(dataOut uintptr, bytesToRead int32, callback *ICefResourceReadCallback) (bytesRead int32, result bool) { if m.bytes != nil && len(m.bytes) > 0 { var i int32 = 0 // 默认 0 diff --git a/cef/local-load-xhr-proxy.go b/cef/local-load-xhr-proxy.go new file mode 100644 index 0000000..fe4816c --- /dev/null +++ b/cef/local-load-xhr-proxy.go @@ -0,0 +1,167 @@ +//---------------------------------------- +// +// Copyright © yanghy. All Rights Reserved. +// +// Licensed under Apache License Version 2.0, January 2004 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +//---------------------------------------- + +package cef + +import ( + "bytes" + "errors" + "fmt" + . "github.com/energye/energy/v2/consts" + "github.com/energye/energy/v2/logger" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "strconv" +) + +var jar, _ = cookiejar.New(nil) + +type IXHRProxy interface { + Send(request *ICefRequest) (*XHRProxyResponse, error) +} + +// XHRProxy +// 数据请求代理 +type XHRProxy struct { + Scheme LocalProxyScheme // http/https/tcp default: http + IP string // default localhost + Port int // default 80 + SSLCert string // /to/path/cert.pem + SSLKey string // /to/path/key.pem +} + +// XHRProxyResponse + +type XHRProxyResponse struct { + Data []byte + DataSize int + StatusCode int32 + Header map[string][]string +} + +func (m *XHRProxy) Send(request *ICefRequest) (*XHRProxyResponse, error) { + if m.Scheme == LpsHttp { + return m.sendHttp(request) + } else if m.Scheme == LpsHttps { + return m.sendHttps(request) + } else if m.Scheme == LpsTcp { + return m.sendTcp(request) + } + return nil, errors.New("incorrect scheme") +} + +func (m *XHRProxy) sendHttp(request *ICefRequest) (*XHRProxyResponse, error) { + reqUrl, err := url.Parse(request.URL()) + if err != nil { + return nil, err + } + targetUrl := new(bytes.Buffer) + targetUrl.WriteString("http") + targetUrl.WriteString("://") + targetUrl.WriteString(m.IP) + if m.Port > 0 { + targetUrl.WriteString(strconv.Itoa(m.Port)) + } + targetUrl.WriteString(reqUrl.Path) + targetUrl.WriteString(reqUrl.RawQuery) + // 读取请求数据 + requestData := new(bytes.Buffer) + postData := request.GetPostData() + if postData.IsValid() { + dataCount := int(postData.GetElementCount()) + elements := postData.GetElements() + for i := 0; i < dataCount; i++ { + element := elements.Get(uint32(i)) + fmt.Println("element-type:", element.GetType()) + switch element.GetType() { + case PDE_TYPE_EMPTY: + case PDE_TYPE_BYTES: + if byt, c := element.GetBytes(); c > 0 { + requestData.Write(byt) + } + case PDE_TYPE_FILE: + if f := element.GetFile(); f != "" { + if byt, err := ioutil.ReadFile(f); err == nil { + requestData.Write(byt) + } + } + } + element.Free() + } + postData.Free() + } + logger.Debug("XHRProxy TargetURL:", targetUrl.String(), "dataLength:", len(requestData.Bytes())) + httpRequest, err := http.NewRequest(request.Method(), targetUrl.String(), requestData) + if err != nil { + return nil, err + } + // 设置请求头 + header := request.GetHeaderMap() + if header.IsValid() { + size := header.GetSize() + for i := 0; i < int(size); i++ { + key := header.GetKey(uint32(i)) + //value := header.GetValue(uint32(i)) + //httpRequest.Header.Add(key, value) + c := header.FindCount(key) + for j := 0; j < int(c); j++ { + value := header.GetEnumerate(key, uint32(j)) + httpRequest.Header.Add(key, value) + fmt.Println("XHRProxy header:", key, "=", value) + } + } + header.Free() + } + // 创建 client + cli := &http.Client{ + Jar: jar, + } + httpResponse, err := cli.Do(httpRequest) + if err != nil { + return nil, err + } + defer httpResponse.Body.Close() + // 读取响应头 + responseHeader := make(map[string][]string) + for k, v := range httpResponse.Header { + for _, vs := range v { + if header, ok := responseHeader[k]; ok { + responseHeader[k] = append(header, vs) + } else { + responseHeader[k] = []string{vs} + } + } + } + // 读取响应数据 + buf := new(bytes.Buffer) + c, err := buf.ReadFrom(httpResponse.Body) + result := &XHRProxyResponse{ + Data: buf.Bytes(), + DataSize: int(c), + StatusCode: int32(httpResponse.StatusCode), + Header: responseHeader, + } + return result, nil +} + +func (m *XHRProxy) sendHttps(request *ICefRequest) (*XHRProxyResponse, error) { + + return nil, errors.New("https unrealized") +} + +func (m *XHRProxy) tcpListen() { + +} + +func (m *XHRProxy) sendTcp(request *ICefRequest) (*XHRProxyResponse, error) { + return nil, errors.New("tcp unrealized") +} diff --git a/cef/types-post-data-element.go b/cef/types-post-data-element.go index 613afbc..0abbd79 100644 --- a/cef/types-post-data-element.go +++ b/cef/types-post-data-element.go @@ -53,6 +53,13 @@ func (m *ICefPostDataElement) IsValid() bool { return true } +func (m *ICefPostDataElement) Free() { + if m.instance != nil { + m.base.Free(m.Instance()) + m.instance = nil + } +} + func (m *ICefPostDataElement) IsReadOnly() bool { if !m.IsValid() { return false diff --git a/cef/types-post-data.go b/cef/types-post-data.go index 62c398a..2572086 100644 --- a/cef/types-post-data.go +++ b/cef/types-post-data.go @@ -51,6 +51,13 @@ func (m *ICefPostData) IsValid() bool { return true } +func (m *ICefPostData) Free() { + if m.instance != nil { + m.base.Free(m.Instance()) + m.instance = nil + } +} + func (m *ICefPostData) IsReadOnly() bool { if !m.IsValid() { return false diff --git a/cef/types-string-multi-map.go b/cef/types-string-multi-map.go index 554806c..099e35c 100644 --- a/cef/types-string-multi-map.go +++ b/cef/types-string-multi-map.go @@ -96,3 +96,10 @@ func (m *ICefStringMultiMap) Append(key, value string) bool { func (m *ICefStringMultiMap) Clear() { imports.Proc(def.StringMultimap_Clear).Call(m.Instance()) } + +func (m *ICefStringMultiMap) Free() { + if m.instance != nil { + m.base.Free(m.Instance()) + m.instance = nil + } +} diff --git a/cef/types.go b/cef/types.go index bc25fbe..7657a41 100644 --- a/cef/types.go +++ b/cef/types.go @@ -465,16 +465,19 @@ type ICefMenuModelDelegate struct { // ICefStringMultiMap 实例 type ICefStringMultiMap struct { + base TCefBaseRefCounted instance unsafe.Pointer } // ICefPostData type ICefPostData struct { + base TCefBaseRefCounted instance unsafe.Pointer } // ICefPostDataElement type ICefPostDataElement struct { + base TCefBaseRefCounted instance unsafe.Pointer } diff --git a/consts/consts.go b/consts/consts.go index 56b1166..80c6e02 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -1591,3 +1591,23 @@ const ( UitGtk3 // linux UitCocoa // macos ) + +// LocalCustomerScheme 本地资源加载自定义固定协议 +// file, fs +type LocalCustomerScheme string + +const ( + LocalCSFile LocalCustomerScheme = "file" // 本地目录 file://energy/index.html + LocalCSFS LocalCustomerScheme = "fs" // 内置 fs://energy/index.html +) + +// LocalProxyScheme +// 本地加载资源,在浏览器发起xhr请求时的代理协议 +// http, https, tcp +type LocalProxyScheme int + +const ( + LpsHttp LocalProxyScheme = iota // http + LpsHttps // https + LpsTcp // tcp +) -- GitLab