-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adds Gzip Processor and Content Inspector (#2)
* feat: add gzip processor * feat: add content inspector * docs: updated condition and process docs * docs: fixed example for the content inspector * docs: add clarification on supported MIME types
- Loading branch information
Showing
10 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package condition | ||
|
||
import ( | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
// Content implements the Inspector interface for evaluating data by content type. More information is available in the README. | ||
type Content struct { | ||
Type string `mapstructure:"type"` | ||
Negate bool `mapstructure:"negate"` | ||
} | ||
|
||
// Inspect evaluates the data by content type. | ||
func (c Content) Inspect(data []byte) (output bool, err error) { | ||
var matched bool | ||
|
||
content := http.DetectContentType(data) | ||
if strings.Compare(content, c.Type) == 0 { | ||
matched = true | ||
} else { | ||
matched = false | ||
} | ||
|
||
if c.Negate { | ||
return !matched, nil | ||
} | ||
|
||
return matched, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package condition | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestContent(t *testing.T) { | ||
var tests = []struct { | ||
inspector Content | ||
test []byte | ||
expected bool | ||
}{ | ||
// matching Gzip against valid Gzip header | ||
{ | ||
Content{ | ||
Type: "application/x-gzip", | ||
}, | ||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255}, | ||
true, | ||
}, | ||
// matching Gzip against invalid Gzip header (bytes swapped) | ||
{ | ||
Content{ | ||
Type: "application/x-gzip", | ||
}, | ||
[]byte{255, 139, 8, 0, 0, 0, 0, 0, 0, 31}, | ||
false, | ||
}, | ||
// matching Gzip against invalid Gzip header (bytes swapped) with negation | ||
{ | ||
Content{ | ||
Type: "application/x-gzip", | ||
Negate: true, | ||
}, | ||
[]byte{255, 139, 8, 0, 0, 0, 0, 0, 0, 31}, | ||
true, | ||
}, | ||
// matching Zip against valid Zip header | ||
{ | ||
Content{ | ||
Type: "application/zip", | ||
}, | ||
[]byte{80, 75, 03, 04}, | ||
true, | ||
}, | ||
// matching Gzip against valid Zip header | ||
{ | ||
Content{ | ||
Type: "application/zip", | ||
}, | ||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255}, | ||
false, | ||
}, | ||
// matching Zip against invalid Zip header (bytes swapped) | ||
{ | ||
Content{ | ||
Type: "application/zip", | ||
}, | ||
[]byte{04, 75, 03, 80}, | ||
false, | ||
}, | ||
} | ||
|
||
for _, testing := range tests { | ||
check, _ := testing.inspector.Inspect(testing.test) | ||
|
||
if testing.expected != check { | ||
t.Logf("expected %v, got %v", testing.expected, check) | ||
t.Fail() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package process | ||
|
||
import ( | ||
"bytes" | ||
"compress/gzip" | ||
"context" | ||
"io/ioutil" | ||
|
||
"github.com/brexhq/substation/condition" | ||
"github.com/brexhq/substation/internal/errors" | ||
) | ||
|
||
// GzipInvalidDirection is used when an invalid direction is given to the processor | ||
const GzipInvalidDirection = errors.Error("GzipInvalidDirection") | ||
|
||
/* | ||
GzipOptions contain custom options settings for this processor. | ||
Direction: the direction of the compression, either to (compress) or from (decompress) Gzip. | ||
*/ | ||
type GzipOptions struct { | ||
Direction string `mapstructure:"direction"` | ||
} | ||
|
||
// Gzip implements the Byter and Channeler interfaces and converts bytes to and from Gzip. More information is available in the README. | ||
type Gzip struct { | ||
Condition condition.OperatorConfig `mapstructure:"condition"` | ||
Options GzipOptions `mapstructure:"options"` | ||
} | ||
|
||
// Channel processes a data channel of bytes with this processor. Conditions can be optionally applied on the channel data to enable processing. | ||
func (p Gzip) Channel(ctx context.Context, ch <-chan []byte) (<-chan []byte, error) { | ||
var array [][]byte | ||
|
||
op, err := condition.OperatorFactory(p.Condition) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for data := range ch { | ||
ok, err := op.Operate(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !ok { | ||
array = append(array, data) | ||
continue | ||
} | ||
|
||
processed, err := p.Byte(ctx, data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
array = append(array, processed) | ||
} | ||
|
||
output := make(chan []byte, len(array)) | ||
for _, x := range array { | ||
output <- x | ||
} | ||
close(output) | ||
return output, nil | ||
} | ||
|
||
// Byte processes a byte slice with this processor. | ||
func (p Gzip) Byte(ctx context.Context, data []byte) ([]byte, error) { | ||
if p.Options.Direction == "from" { | ||
tmp, err := fromGzip(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return tmp, nil | ||
} else if p.Options.Direction == "to" { | ||
tmp, err := toGzip(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return tmp, nil | ||
} else { | ||
return nil, GzipInvalidDirection | ||
} | ||
} | ||
|
||
func fromGzip(data []byte) ([]byte, error) { | ||
r := bytes.NewReader(data) | ||
gz, err := gzip.NewReader(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
output, err := ioutil.ReadAll(gz) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return output, nil | ||
} | ||
|
||
func toGzip(data []byte) ([]byte, error) { | ||
var buf bytes.Buffer | ||
gz := gzip.NewWriter(&buf) | ||
if _, err := gz.Write(data); err != nil { | ||
return nil, err | ||
} | ||
if err := gz.Close(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return buf.Bytes(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package process | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"testing" | ||
) | ||
|
||
func TestGzip(t *testing.T) { | ||
var tests = []struct { | ||
proc Gzip | ||
test []byte | ||
expected []byte | ||
}{ | ||
{ | ||
Gzip{ | ||
Options: GzipOptions{ | ||
Direction: "from", | ||
}, | ||
}, | ||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 170, 86, 202, 72, 205, 201, 201, 87, 178, 82, 74, 207, 207, 79, 73, 170, 76, 85, 170, 5, 4, 0, 0, 255, 255, 214, 182, 196, 150, 19, 0, 0, 0}, | ||
[]byte(`{"hello":"goodbye"}`), | ||
}, | ||
{ | ||
Gzip{ | ||
Options: GzipOptions{ | ||
Direction: "to", | ||
}, | ||
}, | ||
[]byte(`{"hello":"goodbye"}`), | ||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 170, 86, 202, 72, 205, 201, 201, 87, 178, 82, 74, 207, 207, 79, 73, 170, 76, 85, 170, 5, 4, 0, 0, 255, 255, 214, 182, 196, 150, 19, 0, 0, 0}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
ctx := context.TODO() | ||
res, err := test.proc.Byte(ctx, test.test) | ||
if err != nil { | ||
t.Logf("%v", err) | ||
t.Fail() | ||
} | ||
|
||
if c := bytes.Compare(res, test.expected); c != 0 { | ||
t.Logf("expected %s, got %s", test.expected, res) | ||
t.Fail() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.