Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2525cdc061 | ||
|
|
4ffc619aad | ||
|
|
f930705fd7 | ||
|
|
56961c1476 | ||
|
|
28b61d493f | ||
|
|
5cb5ddf2c1 | ||
|
|
fd0bc37ec7 | ||
|
|
73666bea7f |
@@ -24,6 +24,9 @@ type Reader interface {
|
|||||||
// ReadBody 读取Body
|
// ReadBody 读取Body
|
||||||
ReadBody(buf []byte, callback ReaderFunc) error
|
ReadBody(buf []byte, callback ReaderFunc) error
|
||||||
|
|
||||||
|
// Read 实现io.Reader接口
|
||||||
|
Read(buf []byte) (int, error)
|
||||||
|
|
||||||
// ReadBodyRange 读取某个范围内的Body
|
// ReadBodyRange 读取某个范围内的Body
|
||||||
ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error
|
ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,37 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *FileReader) Read(buf []byte) (n int, err error) {
|
||||||
|
var isOk = false
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if !isOk {
|
||||||
|
_ = this.discard()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 直接返回从Header中剩余的
|
||||||
|
if this.bodyBufLen > 0 && len(buf) >= this.bodyBufLen {
|
||||||
|
copy(buf, this.bodyBuf)
|
||||||
|
isOk = true
|
||||||
|
n = this.bodyBufLen
|
||||||
|
|
||||||
|
if this.bodySize <= int64(this.bodyBufLen) {
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bodyBufLen = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = this.fp.Read(buf)
|
||||||
|
if err == nil || err == io.EOF {
|
||||||
|
isOk = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
||||||
isOk := false
|
isOk := false
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemoryReader struct {
|
type MemoryReader struct {
|
||||||
item *MemoryItem
|
item *MemoryItem
|
||||||
|
|
||||||
|
offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryReader(item *MemoryItem) *MemoryReader {
|
func NewMemoryReader(item *MemoryItem) *MemoryReader {
|
||||||
@@ -111,6 +114,33 @@ func (this *MemoryReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *MemoryReader) Read(buf []byte) (n int, err error) {
|
||||||
|
bufLen := len(buf)
|
||||||
|
if bufLen == 0 {
|
||||||
|
return 0, errors.New("using empty buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
bodySize := len(this.item.BodyValue)
|
||||||
|
left := bodySize - this.offset
|
||||||
|
if bufLen <= left {
|
||||||
|
copy(buf, this.item.BodyValue[this.offset:this.offset+bufLen])
|
||||||
|
n = bufLen
|
||||||
|
|
||||||
|
this.offset += bufLen
|
||||||
|
if this.offset >= bodySize {
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
copy(buf, this.item.BodyValue[this.offset:])
|
||||||
|
n = left
|
||||||
|
err = io.EOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (this *MemoryReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
func (this *MemoryReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
||||||
offset := start
|
offset := start
|
||||||
bodySize := int64(len(this.item.BodyValue))
|
bodySize := int64(len(this.item.BodyValue))
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ const (
|
|||||||
HotItemSize = 1024
|
HotItemSize = 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||||
|
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||||
|
|
||||||
// FileStorage 文件缓存
|
// FileStorage 文件缓存
|
||||||
// 文件结构:
|
// 文件结构:
|
||||||
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
|
// [expires time] | [ status ] | [url length] | [header length] | [body length] | [url] [header data] [body data]
|
||||||
@@ -56,10 +59,9 @@ type FileStorage struct {
|
|||||||
memoryStorage *MemoryStorage // 一级缓存
|
memoryStorage *MemoryStorage // 一级缓存
|
||||||
totalSize int64
|
totalSize int64
|
||||||
|
|
||||||
list ListInterface
|
list ListInterface
|
||||||
writingKeyMap map[string]zero.Zero // key => bool
|
locker sync.RWMutex
|
||||||
locker sync.RWMutex
|
purgeTicker *utils.Ticker
|
||||||
purgeTicker *utils.Ticker
|
|
||||||
|
|
||||||
hotMap map[string]*HotItem // key => count
|
hotMap map[string]*HotItem // key => count
|
||||||
hotMapLocker sync.Mutex
|
hotMapLocker sync.Mutex
|
||||||
@@ -69,10 +71,9 @@ type FileStorage struct {
|
|||||||
|
|
||||||
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
|
||||||
return &FileStorage{
|
return &FileStorage{
|
||||||
policy: policy,
|
policy: policy,
|
||||||
writingKeyMap: map[string]zero.Zero{},
|
hotMap: map[string]*HotItem{},
|
||||||
hotMap: map[string]*HotItem{},
|
lastHotSize: -1,
|
||||||
lastHotSize: -1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,21 +315,20 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 是否正在写入
|
// 是否正在写入
|
||||||
var isWriting = false
|
var isOk = false
|
||||||
this.locker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
_, ok := this.writingKeyMap[key]
|
_, ok := sharedWritingFileKeyMap[key]
|
||||||
this.locker.Unlock()
|
|
||||||
if ok {
|
if ok {
|
||||||
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
return nil, ErrFileIsWriting
|
return nil, ErrFileIsWriting
|
||||||
}
|
}
|
||||||
this.locker.Lock()
|
sharedWritingFileKeyMap[key] = zero.New()
|
||||||
this.writingKeyMap[key] = zero.New()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
this.locker.Unlock()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isWriting {
|
if !isOk {
|
||||||
this.locker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(this.writingKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
this.locker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -358,21 +358,27 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查缓存是否已经生成
|
||||||
|
var cachePath = dir + "/" + hash + ".cache"
|
||||||
|
stat, err := os.Stat(cachePath)
|
||||||
|
if err == nil && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
|
||||||
|
// 防止并发连续写入
|
||||||
|
return nil, ErrFileIsWriting
|
||||||
|
}
|
||||||
|
var tmpPath = cachePath + ".tmp"
|
||||||
|
|
||||||
// 先删除
|
// 先删除
|
||||||
err = this.list.Remove(hash)
|
err = this.list.Remove(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := dir + "/" + hash + ".cache.tmp"
|
writer, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_SYNC|os.O_WRONLY, 0666)
|
||||||
writer, err := os.OpenFile(path, os.O_CREATE|os.O_SYNC|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
isWriting = true
|
|
||||||
|
|
||||||
isOk := false
|
var removeOnFailure = true
|
||||||
removeOnFailure := true
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
isOk = false
|
isOk = false
|
||||||
@@ -382,7 +388,7 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
|
|||||||
if !isOk {
|
if !isOk {
|
||||||
_ = writer.Close()
|
_ = writer.Close()
|
||||||
if removeOnFailure {
|
if removeOnFailure {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(tmpPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -453,11 +459,10 @@ func (this *FileStorage) OpenWriter(key string, expiredAt int64, status int) (Wr
|
|||||||
}
|
}
|
||||||
|
|
||||||
isOk = true
|
isOk = true
|
||||||
|
|
||||||
return NewFileWriter(writer, key, expiredAt, func() {
|
return NewFileWriter(writer, key, expiredAt, func() {
|
||||||
this.locker.Lock()
|
sharedWritingFileKeyLocker.Lock()
|
||||||
delete(this.writingKeyMap, key)
|
delete(sharedWritingFileKeyMap, key)
|
||||||
this.locker.Unlock()
|
sharedWritingFileKeyLocker.Unlock()
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func TestFileStorage_Read(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
reader, err := storage.OpenReader("my-key")
|
reader, err := storage.OpenReader("my-key", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
reader, err := storage.OpenReader("my-http-response")
|
reader, err := storage.OpenReader("my-http-response", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -360,7 +360,7 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
buf := make([]byte, 6)
|
buf := make([]byte, 6)
|
||||||
reader, err := storage.OpenReader("my-key-10000")
|
reader, err := storage.OpenReader("my-key-10000", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("cache not fund")
|
t.Log("cache not fund")
|
||||||
@@ -506,7 +506,7 @@ func BenchmarkFileStorage_Read(b *testing.B) {
|
|||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
reader, err := storage.OpenReader("my-key")
|
reader, err := storage.OpenReader("my-key", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
t.Log(storage.valuesMap)
|
t.Log(storage.valuesMap)
|
||||||
|
|
||||||
{
|
{
|
||||||
reader, err := storage.OpenReader("abc")
|
reader, err := storage.OpenReader("abc", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
@@ -52,7 +52,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_, err := storage.OpenReader("abc 2")
|
_, err := storage.OpenReader("abc 2", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc2")
|
t.Log("not found: abc2")
|
||||||
@@ -68,7 +68,7 @@ func TestMemoryStorage_OpenWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, _ = writer.Write([]byte("Hello123"))
|
_, _ = writer.Write([]byte("Hello123"))
|
||||||
{
|
{
|
||||||
reader, err := storage.OpenReader("abc")
|
reader, err := storage.OpenReader("abc", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNotFound {
|
if err == ErrNotFound {
|
||||||
t.Log("not found: abc")
|
t.Log("not found: abc")
|
||||||
@@ -97,7 +97,7 @@ func TestMemoryStorage_OpenReaderLock(t *testing.T) {
|
|||||||
IsDone: true,
|
IsDone: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, _ = storage.OpenReader("test")
|
_, _ = storage.OpenReader("test", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemoryStorage_Delete(t *testing.T) {
|
func TestMemoryStorage_Delete(t *testing.T) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileWriter struct {
|
type FileWriter struct {
|
||||||
@@ -15,6 +16,7 @@ type FileWriter struct {
|
|||||||
bodySize int64
|
bodySize int64
|
||||||
expiredAt int64
|
expiredAt int64
|
||||||
endFunc func()
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWriter(rawWriter *os.File, key string, expiredAt int64, endFunc func()) *FileWriter {
|
func NewFileWriter(rawWriter *os.File, key string, expiredAt int64, endFunc func()) *FileWriter {
|
||||||
@@ -82,18 +84,25 @@ func (this *FileWriter) WriteBodyLength(bodyLength int64) error {
|
|||||||
|
|
||||||
// Close 关闭
|
// Close 关闭
|
||||||
func (this *FileWriter) Close() error {
|
func (this *FileWriter) Close() error {
|
||||||
defer this.endFunc()
|
defer this.once.Do(func() {
|
||||||
|
this.endFunc()
|
||||||
|
})
|
||||||
|
|
||||||
|
path := this.rawWriter.Name()
|
||||||
|
|
||||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = this.rawWriter.Close()
|
||||||
|
_ = os.Remove(path)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = this.WriteBodyLength(this.bodySize)
|
err = this.WriteBodyLength(this.bodySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = this.rawWriter.Close()
|
||||||
|
_ = os.Remove(path)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := this.rawWriter.Name()
|
|
||||||
err = this.rawWriter.Close()
|
err = this.rawWriter.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
@@ -109,7 +118,9 @@ func (this *FileWriter) Close() error {
|
|||||||
|
|
||||||
// Discard 丢弃
|
// Discard 丢弃
|
||||||
func (this *FileWriter) Discard() error {
|
func (this *FileWriter) Discard() error {
|
||||||
defer this.endFunc()
|
defer this.once.Do(func() {
|
||||||
|
this.endFunc()
|
||||||
|
})
|
||||||
|
|
||||||
_ = this.rawWriter.Close()
|
_ = this.rawWriter.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package caches
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ type MemoryWriter struct {
|
|||||||
hash uint64
|
hash uint64
|
||||||
item *MemoryItem
|
item *MemoryItem
|
||||||
endFunc func()
|
endFunc func()
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter {
|
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, endFunc func()) *MemoryWriter {
|
||||||
@@ -66,7 +68,9 @@ func (this *MemoryWriter) BodySize() int64 {
|
|||||||
// Close 关闭
|
// Close 关闭
|
||||||
func (this *MemoryWriter) Close() error {
|
func (this *MemoryWriter) Close() error {
|
||||||
// 需要在Locker之外
|
// 需要在Locker之外
|
||||||
defer this.endFunc()
|
defer this.once.Do(func() {
|
||||||
|
this.endFunc()
|
||||||
|
})
|
||||||
|
|
||||||
if this.item == nil {
|
if this.item == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -92,7 +96,9 @@ func (this *MemoryWriter) Close() error {
|
|||||||
// Discard 丢弃
|
// Discard 丢弃
|
||||||
func (this *MemoryWriter) Discard() error {
|
func (this *MemoryWriter) Discard() error {
|
||||||
// 需要在Locker之外
|
// 需要在Locker之外
|
||||||
defer this.endFunc()
|
defer this.once.Do(func() {
|
||||||
|
this.endFunc()
|
||||||
|
})
|
||||||
|
|
||||||
this.storage.locker.Lock()
|
this.storage.locker.Lock()
|
||||||
delete(this.storage.valuesMap, this.hash)
|
delete(this.storage.valuesMap, this.hash)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package compressions
|
|||||||
import (
|
import (
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BrotliReader struct {
|
type BrotliReader struct {
|
||||||
@@ -16,7 +17,11 @@ func NewBrotliReader(reader io.Reader) (Reader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *BrotliReader) Read(p []byte) (n int, err error) {
|
func (this *BrotliReader) Read(p []byte) (n int, err error) {
|
||||||
return this.reader.Read(p)
|
n, err = this.reader.Read(p)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "excessive") {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *BrotliReader) Close() error {
|
func (this *BrotliReader) Close() error {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.3.7"
|
Version = "0.4.0"
|
||||||
|
|
||||||
ProductName = "Edge Node"
|
ProductName = "Edge Node"
|
||||||
ProcessName = "edge-node"
|
ProcessName = "edge-node"
|
||||||
|
|||||||
1
internal/js/.gitignore
vendored
Normal file
1
internal/js/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/iwind/TeaGo/logs"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Console struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Console) Log(args ...interface{}) {
|
|
||||||
for index, arg := range args {
|
|
||||||
if arg != nil {
|
|
||||||
switch arg.(type) {
|
|
||||||
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
|
|
||||||
default:
|
|
||||||
var argType = reflect.TypeOf(arg)
|
|
||||||
|
|
||||||
// 是否有String()方法,如果有直接调用
|
|
||||||
method, ok := argType.MethodByName("String")
|
|
||||||
if ok && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String {
|
|
||||||
args[index] = method.Func.Call([]reflect.Value{reflect.ValueOf(arg)})[0].String()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转为JSON
|
|
||||||
argJSON, err := this.toJSON(arg)
|
|
||||||
if err != nil {
|
|
||||||
if argType.Kind() == reflect.Func {
|
|
||||||
args[index] = "[function]"
|
|
||||||
} else {
|
|
||||||
args[index] = "[object]"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args[index] = string(argJSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args[index] = "null"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logs.Println(append([]interface{}{"[js][console]"}, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Console) toJSON(o interface{}) ([]byte, error) {
|
|
||||||
return json.Marshal(o)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConsole_Log(t *testing.T) {
|
|
||||||
{
|
|
||||||
vm := NewVM()
|
|
||||||
_, err := vm.RunString("console.log('Hello', 'world')")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
vm := NewVM()
|
|
||||||
_, err := vm.RunString("console.log(null, true, false, 10, 10.123)")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
vm := NewVM()
|
|
||||||
_, err := vm.RunString("console.log({ a:1, b:2 })")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
vm := NewVM()
|
|
||||||
_, err := vm.RunString("console.log(console.log)")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
type HTTP struct {
|
|
||||||
r RequestInterface
|
|
||||||
|
|
||||||
req *Request
|
|
||||||
resp *Response
|
|
||||||
|
|
||||||
onRequest func(req *Request, resp *Response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTP(r RequestInterface) *HTTP {
|
|
||||||
return &HTTP{
|
|
||||||
req: NewRequest(r),
|
|
||||||
resp: NewResponse(r),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HTTP) OnRequest(callback func(req *Request, resp *Response)) {
|
|
||||||
// TODO 考虑是否支持多个callback
|
|
||||||
this.onRequest = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HTTP) OnData(callback func(req *Request, resp *Response)) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HTTP) OnResponse(callback func(req *Request, resp *Response)) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *HTTP) TriggerRequest() {
|
|
||||||
this.onRequest(this.req, this.resp)
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
r RequestInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(r RequestInterface) *Request {
|
|
||||||
return &Request{
|
|
||||||
r: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) Proto() string {
|
|
||||||
return this.r.JSRequest().Proto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) Method() string {
|
|
||||||
return this.r.JSRequest().Method
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) Header() map[string][]string {
|
|
||||||
return this.r.JSRequest().Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) AddHeader(name string, value string) {
|
|
||||||
this.r.JSRequest().Header[name] = append(this.r.JSRequest().Header[name], value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) SetHeader(name string, value string) {
|
|
||||||
this.r.JSRequest().Header[name] = []string{value}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) RemoteAddr() string {
|
|
||||||
var remoteAddr = this.r.JSRequest().RemoteAddr
|
|
||||||
host, _, err := net.SplitHostPort(remoteAddr)
|
|
||||||
if err == nil {
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
return remoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) Url() *URL {
|
|
||||||
return NewURL(this.r.JSRequest().URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) ContentLength() int64 {
|
|
||||||
return this.r.JSRequest().ContentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) Body() []byte {
|
|
||||||
var bodyReader = this.r.JSRequest().Body
|
|
||||||
if bodyReader == nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
this.r.JSLog("read body failed: " + err.Error())
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Request) CopyBody() []byte {
|
|
||||||
var bodyReader = this.r.JSRequest().Body
|
|
||||||
if bodyReader == nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
this.r.JSLog("read body failed: " + err.Error())
|
|
||||||
}
|
|
||||||
this.r.JSRequest().Body = ioutil.NopCloser(bytes.NewReader(data))
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type RequestInterface interface {
|
|
||||||
// JSRequest 请求
|
|
||||||
JSRequest() *http.Request
|
|
||||||
|
|
||||||
// JSWriter 响应
|
|
||||||
JSWriter() http.ResponseWriter
|
|
||||||
|
|
||||||
// JSStop 中止请求
|
|
||||||
JSStop()
|
|
||||||
|
|
||||||
// JSLog 打印日志
|
|
||||||
JSLog(msg ...interface{})
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/js"
|
|
||||||
"github.com/iwind/TeaGo/logs"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testRequest struct {
|
|
||||||
rawRequest *http.Request
|
|
||||||
rawResponse *testResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testRequest) JSRequest() *http.Request {
|
|
||||||
if this.rawRequest != nil {
|
|
||||||
return this.rawRequest
|
|
||||||
}
|
|
||||||
req, _ := http.NewRequest(http.MethodGet, "https://iwind:123456@goedge.cn/docs?name=Libai&age=20", nil)
|
|
||||||
req.Header.Set("Server", "edgejs/1.0")
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Body = ioutil.NopCloser(bytes.NewReader([]byte("123456")))
|
|
||||||
this.rawRequest = req
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testRequest) JSWriter() http.ResponseWriter {
|
|
||||||
if this.rawResponse != nil {
|
|
||||||
return this.rawResponse
|
|
||||||
}
|
|
||||||
this.rawResponse = &testResponse{}
|
|
||||||
return this.rawResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testRequest) JSStop() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testRequest) JSLog(msg ...interface{}) {
|
|
||||||
logs.Println(msg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type testResponse struct {
|
|
||||||
statusCode int
|
|
||||||
header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testResponse) Header() http.Header {
|
|
||||||
if this.header == nil {
|
|
||||||
this.header = http.Header{}
|
|
||||||
}
|
|
||||||
return this.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testResponse) Write(p []byte) (int, error) {
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *testResponse) WriteHeader(statusCode int) {
|
|
||||||
this.statusCode = statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequest(t *testing.T) {
|
|
||||||
vm := js.NewVM()
|
|
||||||
vm.SetRequest(&testRequest{})
|
|
||||||
|
|
||||||
// 事件监听
|
|
||||||
_, err := vm.RunString(`
|
|
||||||
http.onRequest(function (req, resp) {
|
|
||||||
console.log(req.proto())
|
|
||||||
|
|
||||||
let url = req.url()
|
|
||||||
console.log(url, "port:", url.port(), "args:", url.args())
|
|
||||||
console.log("username:", url.username(), "password:", url.password())
|
|
||||||
console.log("uri:", url.uri(), "path:", url.path())
|
|
||||||
|
|
||||||
req.addHeader("Server", "1.0")
|
|
||||||
|
|
||||||
|
|
||||||
resp.write("this is response")
|
|
||||||
console.log(resp)
|
|
||||||
|
|
||||||
console.log(req.body())
|
|
||||||
})
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发事件
|
|
||||||
_, err = vm.RunString(`http.triggerRequest()`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequest_Header(t *testing.T) {
|
|
||||||
var req = js.NewRequest(&testRequest{})
|
|
||||||
logs.PrintAsJSON(req.Header(), t)
|
|
||||||
|
|
||||||
req.AddHeader("Content-Length", "10")
|
|
||||||
req.AddHeader("Vary", "1.0")
|
|
||||||
req.AddHeader("Vary", "2.0")
|
|
||||||
logs.PrintAsJSON(req.Header(), t)
|
|
||||||
|
|
||||||
req.SetHeader("Vary", "3.0")
|
|
||||||
logs.PrintAsJSON(req.Header(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequest_Body(t *testing.T) {
|
|
||||||
var req = js.NewRequest(&testRequest{})
|
|
||||||
t.Log(string(req.Body()))
|
|
||||||
t.Log(string(req.Body()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequest_CopyBody(t *testing.T) {
|
|
||||||
var req = js.NewRequest(&testRequest{})
|
|
||||||
t.Log(string(req.CopyBody()))
|
|
||||||
t.Log(string(req.CopyBody()))
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
r RequestInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResponse(r RequestInterface) *Response {
|
|
||||||
return &Response{
|
|
||||||
r: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) Write(s string) error {
|
|
||||||
_, err := this.r.JSWriter().Write([]byte(s))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) Reply(status int) {
|
|
||||||
this.SetStatus(status)
|
|
||||||
this.r.JSStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) Header() map[string][]string {
|
|
||||||
return this.r.JSWriter().Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) AddHeader(name string, value string) {
|
|
||||||
this.r.JSWriter().Header()[name] = append(this.r.JSWriter().Header()[name], value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) SetHeader(name string, value string) {
|
|
||||||
this.r.JSWriter().Header()[name] = []string{value}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Response) SetStatus(statusCode int) {
|
|
||||||
this.r.JSWriter().WriteHeader(statusCode)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/js"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewResponse(t *testing.T) {
|
|
||||||
var resp = js.NewResponse(&testRequest{})
|
|
||||||
resp.AddHeader("Vary", "1.0")
|
|
||||||
resp.AddHeader("Vary", "2.0")
|
|
||||||
resp.SetHeader("Server", "edgejs/1.0")
|
|
||||||
t.Logf("%#v", resp.Header())
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
"github.com/iwind/TeaGo/types"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
type URL struct {
|
|
||||||
u *url.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewURL(u *url.URL) *URL {
|
|
||||||
return &URL{
|
|
||||||
u: u,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) JSNew(args []goja.Value) *URL {
|
|
||||||
var urlString = ""
|
|
||||||
if len(args) == 1 {
|
|
||||||
urlString = args[0].String()
|
|
||||||
}
|
|
||||||
u, _ := url.Parse(urlString)
|
|
||||||
if u == nil {
|
|
||||||
u = &url.URL{}
|
|
||||||
}
|
|
||||||
return NewURL(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Port() int {
|
|
||||||
return types.Int(this.u.Port())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Args() map[string][]string {
|
|
||||||
return this.u.Query()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Arg(name string) string {
|
|
||||||
return this.u.Query().Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Username() string {
|
|
||||||
if this.u.User != nil {
|
|
||||||
return this.u.User.Username()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Password() string {
|
|
||||||
if this.u.User != nil {
|
|
||||||
password, _ := this.u.User.Password()
|
|
||||||
return password
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Uri() string {
|
|
||||||
return this.u.RequestURI()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Path() string {
|
|
||||||
return this.u.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Host() string {
|
|
||||||
return this.u.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Fragment() string {
|
|
||||||
return this.u.Fragment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Hash() string {
|
|
||||||
if len(this.u.Fragment) > 0 {
|
|
||||||
return "#" + this.u.Fragment
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) Scheme() string {
|
|
||||||
return this.u.Scheme
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *URL) String() string {
|
|
||||||
return this.u.String()
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestURL(t *testing.T) {
|
|
||||||
raw, err := url.Parse("https://iwind:123456@goedge.cn/docs?name=Libai&age=20#a=b")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var u = NewURL(raw)
|
|
||||||
t.Log("host:", u.Host())
|
|
||||||
t.Log("hash:", u.Hash())
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
"github.com/iwind/TeaGo/logs"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sharedPrograms []*goja.Program
|
|
||||||
var sharedConsole = &Console{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// compile programs
|
|
||||||
}
|
|
||||||
|
|
||||||
type VM struct {
|
|
||||||
vm *goja.Runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVM() *VM {
|
|
||||||
vm := goja.New()
|
|
||||||
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
|
||||||
|
|
||||||
// programs
|
|
||||||
for _, program := range sharedPrograms {
|
|
||||||
_, _ = vm.RunProgram(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := &VM{vm: vm}
|
|
||||||
v.initVM()
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) Set(name string, obj interface{}) error {
|
|
||||||
return this.vm.Set(name, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) AddConstructor(name string, instance interface{}) error {
|
|
||||||
objType := reflect.TypeOf(instance)
|
|
||||||
|
|
||||||
if objType.Kind() != reflect.Ptr {
|
|
||||||
return errors.New("instance should be pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct
|
|
||||||
newMethod, ok := objType.MethodByName("JSNew")
|
|
||||||
if !ok {
|
|
||||||
return errors.New("can not find 'JSNew()' method in '" + objType.Elem().Name() + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err = this.Set(name, func(call goja.ConstructorCall) *goja.Object {
|
|
||||||
if newMethod.Type.NumIn() != 2 {
|
|
||||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if newMethod.Type.In(1).String() != "[]goja.Value" {
|
|
||||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// new
|
|
||||||
var results = newMethod.Func.Call([]reflect.Value{reflect.ValueOf(instance), reflect.ValueOf(call.Arguments)})
|
|
||||||
if len(results) == 0 {
|
|
||||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a valid instance"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var result = results[0]
|
|
||||||
if result.Type() != objType {
|
|
||||||
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a same instance"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods
|
|
||||||
var resultType = result.Type()
|
|
||||||
var numMethod = result.NumMethod()
|
|
||||||
for i := 0; i < numMethod; i++ {
|
|
||||||
var method = resultType.Method(i)
|
|
||||||
var methodName = strings.ToLower(method.Name[:1]) + method.Name[1:]
|
|
||||||
err := call.This.Set(methodName, result.MethodByName(method.Name).Interface())
|
|
||||||
if err != nil {
|
|
||||||
this.throw(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 支持属性
|
|
||||||
var numField = result.Elem().Type().NumField()
|
|
||||||
for i := 0; i < numField; i++ {
|
|
||||||
var field = result.Elem().Field(i)
|
|
||||||
if !field.CanInterface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var fieldType = objType.Elem().Field(i)
|
|
||||||
tag, ok := fieldType.Tag.Lookup("json")
|
|
||||||
if !ok {
|
|
||||||
tag = fieldType.Name
|
|
||||||
tag = strings.ToLower(tag[:1]) + tag[1:]
|
|
||||||
} else {
|
|
||||||
// TODO 校验tag是否符合变量语法
|
|
||||||
}
|
|
||||||
err := call.This.Set(tag, field.Interface())
|
|
||||||
if err != nil {
|
|
||||||
this.throw(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) RunString(str string) (goja.Value, error) {
|
|
||||||
defer func() {
|
|
||||||
e := recover()
|
|
||||||
if e != nil {
|
|
||||||
// TODO 需要打印trace
|
|
||||||
logs.Println("panic:", e)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return this.vm.RunString(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) SetRequest(req RequestInterface) {
|
|
||||||
{
|
|
||||||
err := this.vm.Set("http", NewHTTP(req))
|
|
||||||
if err != nil {
|
|
||||||
this.throw(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) initVM() {
|
|
||||||
{
|
|
||||||
err := this.vm.Set("console", sharedConsole)
|
|
||||||
if err != nil {
|
|
||||||
this.throw(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *VM) throw(err error) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
logs.Println("js:VM:error: " + err.Error())
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
|
||||||
|
|
||||||
package js
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewVM(t *testing.T) {
|
|
||||||
before := time.Now()
|
|
||||||
defer func() {
|
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
|
||||||
}()
|
|
||||||
|
|
||||||
vm := NewVM()
|
|
||||||
{
|
|
||||||
v, err := vm.RunString("JSON.stringify({\"a\":\"b\"})")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log("JSON.stringify():", v)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
v, err := vm.RunString(`JSON.parse('{\"a\":\"b\"}')`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log("JSON.parse():", v)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
err := vm.AddConstructor("Url", &URL{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("add constructor error:", err)
|
|
||||||
}
|
|
||||||
_, err = vm.RunString(`
|
|
||||||
{
|
|
||||||
let u = new Url("https://goedge.cn/docs?v=1")
|
|
||||||
console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
|
||||||
console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url()
|
|
||||||
console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url("a", "b", "c")
|
|
||||||
console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("add constructor error:" + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVM_Program(t *testing.T) {
|
|
||||||
var s = `
|
|
||||||
{
|
|
||||||
let u = new Url("https://goedge.cn/docs?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url()
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url("a", "b", "c")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
`
|
|
||||||
program := goja.MustCompile("s", s, true)
|
|
||||||
|
|
||||||
before := time.Now()
|
|
||||||
defer func() {
|
|
||||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
|
||||||
}()
|
|
||||||
|
|
||||||
vm := NewVM()
|
|
||||||
err := vm.AddConstructor("Url", &URL{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("add constructor error:", err)
|
|
||||||
}
|
|
||||||
//_, err = vm.RunString(s)
|
|
||||||
_, err = vm.vm.RunProgram(program)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("add constructor error:" + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_Program(b *testing.B) {
|
|
||||||
var s = `
|
|
||||||
{
|
|
||||||
let u = new Url("https://goedge.cn/docs?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url()
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url("a", "b", "c")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let u = new Url("https://goedge.cn/docs?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let u = new Url("https://teaos.cn/downloads?v=1")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url()
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let u = new Url("a", "b", "c")
|
|
||||||
//console.log("host:", u.host(), u.uri())
|
|
||||||
}
|
|
||||||
`
|
|
||||||
program := goja.MustCompile("s", s, true)
|
|
||||||
|
|
||||||
vm := NewVM()
|
|
||||||
|
|
||||||
err := vm.AddConstructor("Url", &URL{})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal("add constructor error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
//_, err = vm.RunString(s)
|
|
||||||
_, err = vm.vm.RunProgram(program)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal("add constructor error:" + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ func NewClientConn(conn net.Conn, isTLS bool, quickClose bool, globalLimiter *ra
|
|||||||
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
func (this *ClientConn) Read(b []byte) (n int, err error) {
|
||||||
if this.isTLS {
|
if this.isTLS {
|
||||||
if !this.hasRead {
|
if !this.hasRead {
|
||||||
_ = this.rawConn.SetReadDeadline(time.Now().Add(5 * time.Second)) // TODO 握手超时时间可以设置
|
_ = this.rawConn.SetReadDeadline(time.Now().Add(time.Duration(nodeconfigs.DefaultTLSHandshakeTimeout) * time.Second)) // TODO 握手超时时间可以设置
|
||||||
this.hasRead = true
|
this.hasRead = true
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = this.rawConn.SetReadDeadline(time.Time{})
|
_ = this.rawConn.SetReadDeadline(time.Time{})
|
||||||
@@ -48,7 +48,6 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n, err = this.rawConn.Read(b)
|
n, err = this.rawConn.Read(b)
|
||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
atomic.AddUint64(&teaconst.InTrafficBytes, uint64(n))
|
||||||
}
|
}
|
||||||
@@ -66,6 +65,8 @@ func (this *ClientConn) Write(b []byte) (n int, err error) {
|
|||||||
func (this *ClientConn) Close() error {
|
func (this *ClientConn) Close() error {
|
||||||
this.isClosed = true
|
this.isClosed = true
|
||||||
|
|
||||||
|
err := this.rawConn.Close()
|
||||||
|
|
||||||
// 全局并发数限制
|
// 全局并发数限制
|
||||||
this.once.Do(func() {
|
this.once.Do(func() {
|
||||||
if this.globalLimiter != nil {
|
if this.globalLimiter != nil {
|
||||||
@@ -76,7 +77,7 @@ func (this *ClientConn) Close() error {
|
|||||||
// 单个服务并发数限制
|
// 单个服务并发数限制
|
||||||
sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
|
sharedClientConnLimiter.Remove(this.rawConn.RemoteAddr().String())
|
||||||
|
|
||||||
return this.rawConn.Close()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *ClientConn) LocalAddr() net.Addr {
|
func (this *ClientConn) LocalAddr() net.Addr {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type ClientListener struct {
|
|||||||
quickClose bool
|
quickClose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientListener1(listener net.Listener, quickClose bool) *ClientListener {
|
func NewClientListener(listener net.Listener, quickClose bool) *ClientListener {
|
||||||
return &ClientListener{
|
return &ClientListener{
|
||||||
rawListener: listener,
|
rawListener: listener,
|
||||||
quickClose: quickClose,
|
quickClose: quickClose,
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -431,16 +433,28 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else { // 没有Range
|
} else { // 没有Range
|
||||||
|
var body io.Reader = reader
|
||||||
|
var contentEncoding = this.writer.Header().Get("Content-Encoding")
|
||||||
|
if len(contentEncoding) > 0 && !httpAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"), contentEncoding) {
|
||||||
|
decompressReader, err := compressions.NewReader(body, contentEncoding)
|
||||||
|
if err == nil {
|
||||||
|
body = decompressReader
|
||||||
|
defer func() {
|
||||||
|
_ = decompressReader.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
this.writer.Header().Del("Content-Encoding")
|
||||||
|
this.writer.Header().Del("Content-Length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.writer.PrepareCompression(reader.BodySize())
|
this.writer.PrepareCompression(reader.BodySize())
|
||||||
this.writer.WriteHeader(reader.Status())
|
this.writer.WriteHeader(reader.Status())
|
||||||
|
|
||||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
_, err = io.CopyBuffer(this.writer, body, buf)
|
||||||
_, err = this.writer.Write(buf[:n])
|
if err == io.EOF {
|
||||||
if err != nil {
|
err = nil
|
||||||
return false, errWritingToClient
|
}
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.varMapping["cache.status"] = "MISS"
|
this.varMapping["cache.status"] = "MISS"
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"io"
|
"io"
|
||||||
@@ -253,6 +254,24 @@ func (this *HTTPRequest) doReverseProxy() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解压
|
||||||
|
if !resp.Uncompressed {
|
||||||
|
var contentEncoding = resp.Header.Get("Content-Encoding")
|
||||||
|
if len(contentEncoding) > 0 && !httpAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"), contentEncoding) {
|
||||||
|
reader, err := compressions.NewReader(resp.Body, contentEncoding)
|
||||||
|
if err == nil {
|
||||||
|
var body = resp.Body
|
||||||
|
defer func() {
|
||||||
|
_ = body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp.Body = reader
|
||||||
|
resp.Header.Del("Content-Encoding")
|
||||||
|
resp.Header.Del("Content-Length")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 响应Header
|
// 响应Header
|
||||||
this.writer.AddHeaders(resp.Header)
|
this.writer.AddHeaders(resp.Header)
|
||||||
this.processResponseHeaders(resp.StatusCode)
|
this.processResponseHeaders(resp.StatusCode)
|
||||||
|
|||||||
@@ -153,3 +153,19 @@ func httpRequestNextId() string {
|
|||||||
// timestamp + requestId + nodeId
|
// timestamp + requestId + nodeId
|
||||||
return strconv.FormatInt(unixTime, 10) + teaconst.NodeIdString + strconv.Itoa(int(atomic.AddInt32(&httpRequestId, 1)))
|
return strconv.FormatInt(unixTime, 10) + teaconst.NodeIdString + strconv.Itoa(int(atomic.AddInt32(&httpRequestId, 1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否可以接受某个编码
|
||||||
|
func httpAcceptEncoding(acceptEncodings string, encoding string) bool {
|
||||||
|
var pieces = strings.Split(acceptEncodings, ",")
|
||||||
|
for _, piece := range pieces {
|
||||||
|
var qualityIndex = strings.Index(piece, ";")
|
||||||
|
if qualityIndex >= 0 {
|
||||||
|
piece = piece[:qualityIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(piece) == encoding {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (this *Listener) listenTCP() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var netListener = NewClientListener1(tcpListener, protocol.IsHTTPFamily() || protocol.IsHTTPSFamily())
|
var netListener = NewClientListener(tcpListener, protocol.IsHTTPFamily() || protocol.IsHTTPSFamily())
|
||||||
events.On(events.EventQuit, func() {
|
events.On(events.EventQuit, func() {
|
||||||
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
|
remotelogs.Println("LISTENER", "quit "+this.group.FullAddr())
|
||||||
_ = netListener.Close()
|
_ = netListener.Close()
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ func TestHTTPRequestStatManager_Loop_Region(t *testing.T) {
|
|||||||
iplibrary.SharedLibrary = library
|
iplibrary.SharedLibrary = library
|
||||||
|
|
||||||
manager := NewHTTPRequestStatManager()
|
manager := NewHTTPRequestStatManager()
|
||||||
manager.AddRemoteAddr(11, "202.196.0.20")
|
manager.AddRemoteAddr(11, "202.196.0.20", 0, false)
|
||||||
manager.AddRemoteAddr(11, "202.196.0.20") // 重复添加一个测试相加
|
manager.AddRemoteAddr(11, "202.196.0.20", 0, false) // 重复添加一个测试相加
|
||||||
manager.AddRemoteAddr(11, "8.8.8.8")
|
manager.AddRemoteAddr(11, "8.8.8.8", 0, false)
|
||||||
|
|
||||||
/**for i := 0; i < 100; i++ {
|
/**for i := 0; i < 100; i++ {
|
||||||
manager.AddRemoteAddr(11, strconv.Itoa(rands.Int(10, 250))+"."+strconv.Itoa(rands.Int(10, 250))+"."+strconv.Itoa(rands.Int(10, 250))+".8")
|
manager.AddRemoteAddr(11, strconv.Itoa(rands.Int(10, 250))+"."+strconv.Itoa(rands.Int(10, 250))+"."+strconv.Itoa(rands.Int(10, 250))+".8")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func TestTrafficStatManager_Add(t *testing.T) {
|
func TestTrafficStatManager_Add(t *testing.T) {
|
||||||
manager := NewTrafficStatManager()
|
manager := NewTrafficStatManager()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0)
|
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0, false, 0)
|
||||||
}
|
}
|
||||||
t.Log(manager.itemMap)
|
t.Log(manager.itemMap)
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ func TestTrafficStatManager_Add(t *testing.T) {
|
|||||||
func TestTrafficStatManager_Upload(t *testing.T) {
|
func TestTrafficStatManager_Upload(t *testing.T) {
|
||||||
manager := NewTrafficStatManager()
|
manager := NewTrafficStatManager()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0)
|
manager.Add(1, "goedge.cn", 1, 0, 0, 0, 0, 0, false, 0)
|
||||||
}
|
}
|
||||||
err := manager.Upload()
|
err := manager.Upload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -30,6 +30,6 @@ func BenchmarkTrafficStatManager_Add(b *testing.B) {
|
|||||||
|
|
||||||
manager := NewTrafficStatManager()
|
manager := NewTrafficStatManager()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
manager.Add(1, "goedge.cn", 1024, 1, 0, 0, 0, 0)
|
manager.Add(1, "goedge.cn", 1024, 1, 0, 0, 0, 0, false, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ttlcache
|
package ttlcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,12 +22,18 @@ type Cache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewCache(opt ...OptionInterface) *Cache {
|
func NewCache(opt ...OptionInterface) *Cache {
|
||||||
countPieces := 128
|
var countPieces = 128
|
||||||
maxItems := 2_000_000
|
var maxItems = 2_000_000
|
||||||
|
|
||||||
var delta = systemMemoryGB() / 8
|
var totalMemory = utils.SystemMemoryGB()
|
||||||
if delta > 0 {
|
if totalMemory < 2 {
|
||||||
maxItems *= delta
|
// 我们限制内存过小的服务能够使用的数量
|
||||||
|
maxItems = 1_000_000
|
||||||
|
} else {
|
||||||
|
var delta = totalMemory / 8
|
||||||
|
if delta > 0 {
|
||||||
|
maxItems *= delta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, option := range opt {
|
for _, option := range opt {
|
||||||
|
|||||||
@@ -29,12 +29,19 @@ func TestNewCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCache_Memory(t *testing.T) {
|
func TestCache_Memory(t *testing.T) {
|
||||||
|
var stat1 = &runtime.MemStats{}
|
||||||
|
runtime.ReadMemStats(stat1)
|
||||||
|
|
||||||
cache := NewCache()
|
cache := NewCache()
|
||||||
for i := 0; i < 20_000_000; i++ {
|
for i := 0; i < 10_000_000; i++ {
|
||||||
cache.Write("a"+strconv.Itoa(i), 1, time.Now().Unix()+3600)
|
cache.Write("a"+strconv.Itoa(i), 1, time.Now().Unix()+3600)
|
||||||
}
|
}
|
||||||
t.Log("waiting ...")
|
t.Log("waiting ...")
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
|
var stat2 = &runtime.MemStats{}
|
||||||
|
runtime.ReadMemStats(stat2)
|
||||||
|
|
||||||
|
t.Log((stat2.HeapInuse-stat1.HeapInuse)/1024/1024, "MB")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCache_Add(b *testing.B) {
|
func BenchmarkCache_Add(b *testing.B) {
|
||||||
|
|||||||
@@ -20,8 +20,14 @@ func NewPiece(maxItems int) *Piece {
|
|||||||
func (this *Piece) Add(key uint64, item *Item) (ok bool) {
|
func (this *Piece) Add(key uint64, item *Item) (ok bool) {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
if len(this.m) >= this.maxItems {
|
if len(this.m) >= this.maxItems {
|
||||||
this.locker.Unlock()
|
// 尝试先删除过期的
|
||||||
return
|
this.gcWithoutLocker()
|
||||||
|
|
||||||
|
// 仍然是满的就跳过
|
||||||
|
if len(this.m) >= this.maxItems {
|
||||||
|
this.locker.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.m[key] = item
|
this.m[key] = item
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
@@ -74,12 +80,7 @@ func (this *Piece) Count() (count int) {
|
|||||||
|
|
||||||
func (this *Piece) GC() {
|
func (this *Piece) GC() {
|
||||||
this.locker.Lock()
|
this.locker.Lock()
|
||||||
timestamp := time.Now().Unix()
|
this.gcWithoutLocker()
|
||||||
for k, item := range this.m {
|
|
||||||
if item.expiredAt <= timestamp {
|
|
||||||
delete(this.m, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,3 +95,13 @@ func (this *Piece) Destroy() {
|
|||||||
this.m = nil
|
this.m = nil
|
||||||
this.locker.Unlock()
|
this.locker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 不加锁的gc
|
||||||
|
func (this *Piece) gcWithoutLocker() {
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
for k, item := range this.m {
|
||||||
|
if item.expiredAt <= timestamp {
|
||||||
|
delete(this.m, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package ttlcache
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/mem"
|
||||||
@@ -8,7 +8,11 @@ import (
|
|||||||
|
|
||||||
var systemTotalMemory = -1
|
var systemTotalMemory = -1
|
||||||
|
|
||||||
func systemMemoryGB() int {
|
func init() {
|
||||||
|
_ = SystemMemoryGB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SystemMemoryGB() int {
|
||||||
if systemTotalMemory > 0 {
|
if systemTotalMemory > 0 {
|
||||||
return systemTotalMemory
|
return systemTotalMemory
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package ttlcache
|
package utils
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestSystemMemoryGB(t *testing.T) {
|
func TestSystemMemoryGB(t *testing.T) {
|
||||||
t.Log(systemMemoryGB())
|
t.Log(SystemMemoryGB())
|
||||||
t.Log(systemMemoryGB())
|
t.Log(SystemMemoryGB())
|
||||||
t.Log(systemMemoryGB())
|
t.Log(SystemMemoryGB())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user