mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-12 20:26:37 +08:00
init
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/recorder"
|
||||
)
|
||||
|
||||
type fileRecorderOptions struct {
|
||||
sep string
|
||||
}
|
||||
|
||||
type FileRecorderOption func(opts *fileRecorderOptions)
|
||||
|
||||
func SepRecorderOption(sep string) FileRecorderOption {
|
||||
return func(opts *fileRecorderOptions) {
|
||||
opts.sep = sep
|
||||
}
|
||||
}
|
||||
|
||||
type fileRecorder struct {
|
||||
out io.WriteCloser
|
||||
sep string
|
||||
}
|
||||
|
||||
// FileRecorder records data to file.
|
||||
func FileRecorder(out io.WriteCloser, opts ...FileRecorderOption) recorder.Recorder {
|
||||
var options fileRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &fileRecorder{
|
||||
out: out,
|
||||
sep: options.sep,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *fileRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if _, err := r.out.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.sep != "" {
|
||||
_, err := io.WriteString(r.out, r.sep)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fileRecorder) Close() error {
|
||||
return r.out.Close()
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/recorder"
|
||||
)
|
||||
|
||||
type httpRecorderOptions struct {
|
||||
timeout time.Duration
|
||||
header http.Header
|
||||
}
|
||||
|
||||
type HTTPRecorderOption func(opts *httpRecorderOptions)
|
||||
|
||||
func TimeoutHTTPRecorderOption(timeout time.Duration) HTTPRecorderOption {
|
||||
return func(opts *httpRecorderOptions) {
|
||||
opts.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func HeaderHTTPRecorderOption(header http.Header) HTTPRecorderOption {
|
||||
return func(opts *httpRecorderOptions) {
|
||||
opts.header = header
|
||||
}
|
||||
}
|
||||
|
||||
type httpRecorder struct {
|
||||
url string
|
||||
httpClient *http.Client
|
||||
header http.Header
|
||||
}
|
||||
|
||||
// HTTPRecorder records data to HTTP service.
|
||||
func HTTPRecorder(url string, opts ...HTTPRecorderOption) recorder.Recorder {
|
||||
var options httpRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
return &httpRecorder{
|
||||
url: url,
|
||||
httpClient: &http.Client{
|
||||
Timeout: options.timeout,
|
||||
},
|
||||
header: options.header,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *httpRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
req, err := http.NewRequest(http.MethodPost, r.url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.header != nil {
|
||||
req.Header = r.header
|
||||
}
|
||||
|
||||
if req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-gost/plugin/recorder/proto"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type grpcPlugin struct {
|
||||
conn grpc.ClientConnInterface
|
||||
client proto.RecorderClient
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewGRPCPlugin creates a Recorder plugin based on gRPC.
|
||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) recorder.Recorder {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "recorder",
|
||||
"recorder": name,
|
||||
})
|
||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
p := &grpcPlugin{
|
||||
conn: conn,
|
||||
log: log,
|
||||
}
|
||||
if conn != nil {
|
||||
p.client = proto.NewRecorderClient(conn)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if p.client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var options recorder.RecordOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
md, _ := json.Marshal(options.Metadata)
|
||||
|
||||
reply, err := p.client.Record(ctx,
|
||||
&proto.RecordRequest{
|
||||
Data: b,
|
||||
Metadata: md,
|
||||
})
|
||||
if err != nil {
|
||||
p.log.Error(err)
|
||||
return err
|
||||
}
|
||||
if reply == nil || !reply.Ok {
|
||||
return errors.New("record failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Close() error {
|
||||
if p.conn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if closer, ok := p.conn.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
type httpPluginRequest struct {
|
||||
Data []byte `json:"data"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
}
|
||||
|
||||
type httpPluginResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
url string
|
||||
client *http.Client
|
||||
header http.Header
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewHTTPPlugin creates an Recorder plugin based on HTTP.
|
||||
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) recorder.Recorder {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &httpPlugin{
|
||||
url: url,
|
||||
client: plugin.NewHTTPClient(&options),
|
||||
header: options.Header,
|
||||
log: logger.Default().WithFields(map[string]any{
|
||||
"kind": "recorder",
|
||||
"recorder": name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if len(b) == 0 || p.client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var options recorder.RecordOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
md, _ := json.Marshal(options.Metadata)
|
||||
|
||||
rb := httpPluginRequest{
|
||||
Data: b,
|
||||
Metadata: md,
|
||||
}
|
||||
v, err := json.Marshal(&rb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.header != nil {
|
||||
req.Header = p.header.Clone()
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
res := httpPluginResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !res.OK {
|
||||
return errors.New("record failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/recorder"
|
||||
)
|
||||
|
||||
const (
|
||||
RecorderServiceHandler = "recorder.service.handler"
|
||||
RecorderServiceHandlerSerial = "recorder.service.handler.serial"
|
||||
RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel"
|
||||
)
|
||||
|
||||
type HTTPRequestRecorderObject struct {
|
||||
ContentLength int64 `json:"contentLength"`
|
||||
Header http.Header `json:"header"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
type HTTPResponseRecorderObject struct {
|
||||
ContentLength int64 `json:"contentLength"`
|
||||
Header http.Header `json:"header"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
type HTTPRecorderObject struct {
|
||||
Host string `json:"host"`
|
||||
Method string `json:"method"`
|
||||
Proto string `json:"proto"`
|
||||
Scheme string `json:"scheme"`
|
||||
URI string `json:"uri"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Request HTTPRequestRecorderObject `json:"request"`
|
||||
Response HTTPResponseRecorderObject `json:"response"`
|
||||
}
|
||||
|
||||
type WebsocketRecorderObject struct {
|
||||
From string `json:"from"`
|
||||
Fin bool `json:"fin"`
|
||||
Rsv1 bool `json:"rsv1"`
|
||||
Rsv2 bool `json:"rsv2"`
|
||||
Rsv3 bool `json:"rsv3"`
|
||||
OpCode int `json:"opcode"`
|
||||
Masked bool `json:"masked"`
|
||||
MaskKey uint32 `json:"maskKey"`
|
||||
Length int64 `json:"length"`
|
||||
Payload []byte `json:"payload"`
|
||||
}
|
||||
|
||||
type TLSRecorderObject struct {
|
||||
ServerName string `json:"serverName"`
|
||||
CipherSuite string `json:"cipherSuite"`
|
||||
CompressionMethod uint8 `json:"compressionMethod"`
|
||||
Proto string `json:"proto"`
|
||||
Version string `json:"version"`
|
||||
ClientHello string `json:"clientHello"`
|
||||
ServerHello string `json:"serverHello"`
|
||||
}
|
||||
|
||||
type DNSRecorderObject struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Class string `json:"class"`
|
||||
Type string `json:"type"`
|
||||
Question string `json:"question"`
|
||||
Answer string `json:"answer"`
|
||||
Cached bool `json:"cached"`
|
||||
}
|
||||
|
||||
type HandlerRecorderObject struct {
|
||||
Node string `json:"node,omitempty"`
|
||||
Service string `json:"service"`
|
||||
Network string `json:"network"`
|
||||
RemoteAddr string `json:"remote"`
|
||||
LocalAddr string `json:"local"`
|
||||
Host string `json:"host"`
|
||||
Dst string `json:"dst"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
ClientIP string `json:"clientIP"`
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
HTTP *HTTPRecorderObject `json:"http,omitempty"`
|
||||
Websocket *WebsocketRecorderObject `json:"websocket,omitempty"`
|
||||
TLS *TLSRecorderObject `json:"tls,omitempty"`
|
||||
DNS *DNSRecorderObject `json:"dns,omitempty"`
|
||||
Route string `json:"route,omitempty"`
|
||||
InputBytes uint64 `json:"inputBytes"`
|
||||
OutputBytes uint64 `json:"outputBytes"`
|
||||
Redirect string `json:"redirect,omitempty"`
|
||||
Err string `json:"err,omitempty"`
|
||||
SID string `json:"sid"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error {
|
||||
if p == nil || r == nil || p.Time.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Record(ctx, data)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
type redisRecorderOptions struct {
|
||||
db int
|
||||
username string
|
||||
password string
|
||||
key string
|
||||
}
|
||||
type RedisRecorderOption func(opts *redisRecorderOptions)
|
||||
|
||||
func DBRedisRecorderOption(db int) RedisRecorderOption {
|
||||
return func(opts *redisRecorderOptions) {
|
||||
opts.db = db
|
||||
}
|
||||
}
|
||||
|
||||
func UsernameRedisRecorderOption(username string) RedisRecorderOption {
|
||||
return func(opts *redisRecorderOptions) {
|
||||
opts.username = username
|
||||
}
|
||||
}
|
||||
func PasswordRedisRecorderOption(password string) RedisRecorderOption {
|
||||
return func(opts *redisRecorderOptions) {
|
||||
opts.password = password
|
||||
}
|
||||
}
|
||||
|
||||
func KeyRedisRecorderOption(key string) RedisRecorderOption {
|
||||
return func(opts *redisRecorderOptions) {
|
||||
opts.key = key
|
||||
}
|
||||
}
|
||||
|
||||
type redisSetRecorder struct {
|
||||
client *redis.Client
|
||||
key string
|
||||
}
|
||||
|
||||
// RedisSetRecorder records data to a redis set.
|
||||
func RedisSetRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
|
||||
var options redisRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &redisSetRecorder{
|
||||
client: redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: options.password,
|
||||
DB: options.db,
|
||||
}),
|
||||
key: options.key,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisSetRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if r.key == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.client.SAdd(ctx, r.key, b).Err()
|
||||
}
|
||||
|
||||
func (r *redisSetRecorder) Close() error {
|
||||
return r.client.Close()
|
||||
}
|
||||
|
||||
type redisListRecorder struct {
|
||||
client *redis.Client
|
||||
key string
|
||||
}
|
||||
|
||||
// RedisListRecorder records data to a redis list.
|
||||
func RedisListRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
|
||||
var options redisRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &redisListRecorder{
|
||||
client: redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: options.password,
|
||||
DB: options.db,
|
||||
}),
|
||||
key: options.key,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisListRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if r.key == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.client.LPush(ctx, r.key, b).Err()
|
||||
}
|
||||
|
||||
func (r *redisListRecorder) Close() error {
|
||||
return r.client.Close()
|
||||
}
|
||||
|
||||
type redisSortedSetRecorder struct {
|
||||
client *redis.Client
|
||||
key string
|
||||
}
|
||||
|
||||
// RedisSortedSetRecorder records data to a redis sorted set.
|
||||
func RedisSortedSetRecorder(addr string, opts ...RedisRecorderOption) recorder.Recorder {
|
||||
var options redisRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &redisSortedSetRecorder{
|
||||
client: redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: options.password,
|
||||
DB: options.db,
|
||||
}),
|
||||
key: options.key,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisSortedSetRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
if r.key == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.client.ZIncr(ctx, r.key, &redis.Z{
|
||||
Score: 1,
|
||||
Member: b,
|
||||
}).Err()
|
||||
}
|
||||
|
||||
func (r *redisSortedSetRecorder) Close() error {
|
||||
return r.client.Close()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/recorder"
|
||||
)
|
||||
|
||||
type tcpRecorderOptions struct {
|
||||
timeout time.Duration
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
type TCPRecorderOption func(opts *tcpRecorderOptions)
|
||||
|
||||
func TimeoutTCPRecorderOption(timeout time.Duration) TCPRecorderOption {
|
||||
return func(opts *tcpRecorderOptions) {
|
||||
opts.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func LogTCPRecorderOption(log logger.Logger) TCPRecorderOption {
|
||||
return func(opts *tcpRecorderOptions) {
|
||||
opts.log = log
|
||||
}
|
||||
}
|
||||
|
||||
type tcpRecorder struct {
|
||||
addr string
|
||||
dialer *net.Dialer
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// TCPRecorder records data to TCP service.
|
||||
func TCPRecorder(addr string, opts ...TCPRecorderOption) recorder.Recorder {
|
||||
var options tcpRecorderOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &tcpRecorder{
|
||||
addr: addr,
|
||||
dialer: &net.Dialer{
|
||||
Timeout: options.timeout,
|
||||
},
|
||||
log: options.log,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tcpRecorder) Record(ctx context.Context, b []byte, opts ...recorder.RecordOption) error {
|
||||
c, err := r.dialer.DialContext(ctx, "tcp", r.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
_, err = c.Write(b)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user