mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
New portion of changes
This commit is contained in:
+19
-13
@@ -7,8 +7,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const InitialDynamicWorkers = 10
|
||||
const initialDynamicWorkers = 10
|
||||
|
||||
// HTTPOutputConfig struct for holding http output configuration
|
||||
type HTTPOutputConfig struct {
|
||||
redirectLimit int
|
||||
|
||||
@@ -20,6 +21,9 @@ type HTTPOutputConfig struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// HTTPOutput plugin manage pool of workers which send request to replayed server
|
||||
// By default workers pool is dynamic and starts with 10 workers
|
||||
// You can specify fixed number of workers using `--output-http-workers`
|
||||
type HTTPOutput struct {
|
||||
// Keep this as first element of struct because it guarantees 64bit
|
||||
// alignment. atomic.* functions crash on 32bit machines if operand is not
|
||||
@@ -39,6 +43,8 @@ type HTTPOutput struct {
|
||||
elasticSearch *ESPlugin
|
||||
}
|
||||
|
||||
// NewHTTPOutput constructor for HTTPOutput
|
||||
// Initialize workers
|
||||
func NewHTTPOutput(address string, config *HTTPOutputConfig) io.Writer {
|
||||
|
||||
o := new(HTTPOutput)
|
||||
@@ -55,7 +61,7 @@ func NewHTTPOutput(address string, config *HTTPOutputConfig) io.Writer {
|
||||
|
||||
// Initial workers count
|
||||
if o.config.workers == 0 {
|
||||
o.needWorker <- InitialDynamicWorkers
|
||||
o.needWorker <- initialDynamicWorkers
|
||||
} else {
|
||||
o.needWorker <- o.config.workers
|
||||
}
|
||||
@@ -65,16 +71,16 @@ func NewHTTPOutput(address string, config *HTTPOutputConfig) io.Writer {
|
||||
o.elasticSearch.Init(o.config.elasticSearch)
|
||||
}
|
||||
|
||||
go o.WorkerMaster()
|
||||
go o.workerMaster()
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *HTTPOutput) WorkerMaster() {
|
||||
func (o *HTTPOutput) workerMaster() {
|
||||
for {
|
||||
new_workers := <-o.needWorker
|
||||
for i := 0; i < new_workers; i++ {
|
||||
go o.Worker()
|
||||
newWorkers := <-o.needWorker
|
||||
for i := 0; i < newWorkers; i++ {
|
||||
go o.startWorker()
|
||||
}
|
||||
|
||||
// Disable dynamic scaling if workers poll fixed size
|
||||
@@ -84,13 +90,13 @@ func (o *HTTPOutput) WorkerMaster() {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *HTTPOutput) Worker() {
|
||||
func (o *HTTPOutput) startWorker() {
|
||||
client := NewHTTPClient(o.address, &HTTPClientConfig{
|
||||
FollowRedirects: o.config.redirectLimit,
|
||||
Debug: o.config.Debug,
|
||||
})
|
||||
|
||||
death_count := 0
|
||||
deathCount := 0
|
||||
|
||||
atomic.AddInt64(&o.activeWorkers, 1)
|
||||
|
||||
@@ -98,19 +104,19 @@ func (o *HTTPOutput) Worker() {
|
||||
select {
|
||||
case data := <-o.queue:
|
||||
o.sendRequest(client, data)
|
||||
death_count = 0
|
||||
deathCount = 0
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
// When dynamic scaling enabled workers die after 2s of inactivity
|
||||
if o.config.workers == 0 {
|
||||
death_count += 1
|
||||
deathCount++
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if death_count > 20 {
|
||||
if deathCount > 20 {
|
||||
workersCount := atomic.LoadInt64(&o.activeWorkers)
|
||||
|
||||
// At least 1 worker should be alive
|
||||
// At least 1 startWorker should be alive
|
||||
if workersCount != 1 {
|
||||
atomic.AddInt64(&o.activeWorkers, -1)
|
||||
return
|
||||
|
||||
+2
-2
@@ -86,10 +86,10 @@ func TestOutputHTTPSSL(t *testing.T) {
|
||||
}))
|
||||
|
||||
input := NewTestInput()
|
||||
http_output := NewHTTPOutput(server.URL, &HTTPOutputConfig{})
|
||||
output := NewHTTPOutput(server.URL, &HTTPOutputConfig{})
|
||||
|
||||
Plugins.Inputs = []io.Reader{input}
|
||||
Plugins.Outputs = []io.Writer{http_output}
|
||||
Plugins.Outputs = []io.Writer{output}
|
||||
|
||||
go Start(quit)
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TCPOutput used for sending raw tcp payloads
|
||||
// Currently used for internal communication between listener and replay server
|
||||
// Can be used for transfering binary payloads like protocol buffers
|
||||
type TCPOutput struct {
|
||||
address string
|
||||
limit int
|
||||
@@ -16,6 +19,8 @@ type TCPOutput struct {
|
||||
bufStats *GorStat
|
||||
}
|
||||
|
||||
// NewTCPOutput constructor for TCPOutput
|
||||
// Initialize 10 workers which hold keep-alive connection
|
||||
func NewTCPOutput(address string) io.Writer {
|
||||
o := new(TCPOutput)
|
||||
|
||||
|
||||
+13
-10
@@ -6,23 +6,25 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// InOutPlugins struct for holding references to plugins
|
||||
type InOutPlugins struct {
|
||||
Inputs []io.Reader
|
||||
Outputs []io.Writer
|
||||
}
|
||||
|
||||
type ReaderOrWriter interface{}
|
||||
|
||||
var Plugins *InOutPlugins = new(InOutPlugins)
|
||||
// Plugins holds all the plugin objects
|
||||
var Plugins *InOutPlugins
|
||||
|
||||
// extractLimitOptions detects if plugin get called with limiter support
|
||||
// Returns address and limit
|
||||
func extractLimitOptions(options string) (string, string) {
|
||||
split := strings.Split(options, "|")
|
||||
|
||||
if len(split) > 1 {
|
||||
return split[0], split[1]
|
||||
} else {
|
||||
return split[0], ""
|
||||
}
|
||||
|
||||
return split[0], ""
|
||||
}
|
||||
|
||||
// Automatically detects type of plugin and initialize it
|
||||
@@ -45,23 +47,24 @@ func registerPlugin(constructor interface{}, options ...interface{}) {
|
||||
|
||||
// Calling our constructor with list of given options
|
||||
plugin := vc.Call(vo)[0].Interface()
|
||||
plugin_wrapper := plugin
|
||||
pluginWrapper := plugin
|
||||
|
||||
if limit != "" {
|
||||
plugin_wrapper = NewLimiter(plugin, limit)
|
||||
pluginWrapper = NewLimiter(plugin, limit)
|
||||
} else {
|
||||
plugin_wrapper = plugin
|
||||
pluginWrapper = plugin
|
||||
}
|
||||
|
||||
if _, ok := plugin.(io.Reader); ok {
|
||||
Plugins.Inputs = append(Plugins.Inputs, plugin_wrapper.(io.Reader))
|
||||
Plugins.Inputs = append(Plugins.Inputs, pluginWrapper.(io.Reader))
|
||||
}
|
||||
|
||||
if _, ok := plugin.(io.Writer); ok {
|
||||
Plugins.Outputs = append(Plugins.Outputs, plugin_wrapper.(io.Writer))
|
||||
Plugins.Outputs = append(Plugins.Outputs, pluginWrapper.(io.Writer))
|
||||
}
|
||||
}
|
||||
|
||||
// InitPlugins specify and initialize all available plugins
|
||||
func InitPlugins() {
|
||||
for _, options := range Settings.inputDummy {
|
||||
registerPlugin(NewDummyInput, options)
|
||||
|
||||
+7
-2
@@ -8,21 +8,24 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// VERSION specifies Gor current version
|
||||
VERSION = "0.9.8"
|
||||
)
|
||||
|
||||
// Allows to specify multiple flags with same name and collects all values to array
|
||||
// MultiOption allows to specify multiple flags with same name and collects all values into array
|
||||
type MultiOption []string
|
||||
|
||||
func (h *MultiOption) String() string {
|
||||
return fmt.Sprint(*h)
|
||||
}
|
||||
|
||||
// Set gets called multiple times for each flag with same name
|
||||
func (h *MultiOption) Set(value string) error {
|
||||
*h = append(*h, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppSettings is the struct of main configuration
|
||||
type AppSettings struct {
|
||||
verbose bool
|
||||
debug bool
|
||||
@@ -49,7 +52,8 @@ type AppSettings struct {
|
||||
modifierConfig HTTPModifierConfig
|
||||
}
|
||||
|
||||
var Settings AppSettings = AppSettings{}
|
||||
// Settings holds Gor configuration
|
||||
var Settings AppSettings
|
||||
|
||||
func usage() {
|
||||
fmt.Printf("Gor is a simple http traffic replication tool written in Go. Its main goal is to replay traffic from production servers to staging and dev environments.\nProject page: https://github.com/buger/gor\nAuthor: <Leonid Bugaev> leonsbox@gmail.com\nCurrent Version: %s\n\n", VERSION)
|
||||
@@ -113,6 +117,7 @@ func init() {
|
||||
flag.Var(&Settings.modifierConfig.paramHashFilters, "http-param-limiter", "Takes a fraction of requests, consistently taking or rejecting a request based on the FNV32-1A hash of a specific GET param:\n\t gor --input-raw :8080 --output-http staging.com --http-param-limiter user_id:25%")
|
||||
}
|
||||
|
||||
// Debug gets called only if --verbose flag specified
|
||||
func Debug(args ...interface{}) {
|
||||
if Settings.verbose {
|
||||
fmt.Print("[DEBUG] ")
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// TestInput used for testing purpose, it allows emitting requests on demand
|
||||
type TestInput struct {
|
||||
data chan []byte
|
||||
}
|
||||
|
||||
// NewTestInput constructor for TestInput
|
||||
func NewTestInput() (i *TestInput) {
|
||||
i = new(TestInput)
|
||||
i.data = make(chan []byte, 100)
|
||||
@@ -23,18 +25,22 @@ func (i *TestInput) Read(data []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// EmitGET emits GET request without headers
|
||||
func (i *TestInput) EmitGET() {
|
||||
i.data <- []byte("GET / HTTP/1.1\r\n\r\n")
|
||||
}
|
||||
|
||||
// EmitPOST emits POST request with Content-Length
|
||||
func (i *TestInput) EmitPOST() {
|
||||
i.data <- []byte("POST /pub/WWW/ HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
||||
}
|
||||
|
||||
// EmitChunkedPOST emits POST request with `Transfer-Encoding: chunked` and chunked body
|
||||
func (i *TestInput) EmitChunkedPOST() {
|
||||
i.data <- []byte("POST /pub/WWW/ HTTP/1.1\r\nHost: www.w3.org\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nWiki\r\n5\r\npedia\r\ne\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n")
|
||||
}
|
||||
|
||||
// EmitLargePOST emits POST request with large payload (5mb)
|
||||
func (i *TestInput) EmitLargePOST() {
|
||||
size := 5 * 1024 * 1024 // 5 MB
|
||||
rb := make([]byte, size)
|
||||
@@ -45,6 +51,7 @@ func (i *TestInput) EmitLargePOST() {
|
||||
i.data <- []byte("POST / HTTP/1.1\nHost: www.w3.org\nContent-Length:5242880\r\n\r\n" + rs)
|
||||
}
|
||||
|
||||
// EmitOPTIONS emits OPTIONS request, similar to GET
|
||||
func (i *TestInput) EmitOPTIONS() {
|
||||
i.data <- []byte("OPTIONS / HTTP/1.1\nHost: www.w3.org\r\n\r\n")
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
type writeCallback func(data []byte)
|
||||
|
||||
// TestOutput used in testing to intercept any output into callback
|
||||
type TestOutput struct {
|
||||
cb writeCallback
|
||||
}
|
||||
|
||||
// NewTestOutput constructor for TestOutput, accepts callback which get called on each incoming Write
|
||||
func NewTestOutput(cb writeCallback) (i *TestOutput) {
|
||||
i = new(TestOutput)
|
||||
i.cb = cb
|
||||
|
||||
Reference in New Issue
Block a user