-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Support encoding/emitting nested structures with binary fields #1
base: main
Are you sure you want to change the base?
Changes from 1 commit
4597211
111a35d
005578a
379e950
b80f54d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
package parser | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"unsafe" | ||
|
||
jsoniter "github.com/json-iterator/go" | ||
"github.com/mitchellh/mapstructure" | ||
"github.com/zishang520/engine.io-go-parser/types" | ||
) | ||
|
@@ -13,54 +15,46 @@ type Placeholder struct { | |
Num int `json:"num" mapstructure:"num" msgpack:"num"` | ||
} | ||
|
||
func init() { | ||
jsoniter.RegisterTypeEncoderFunc("types.BytesBuffer", func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | ||
bb := ((*types.BytesBuffer)(ptr)) | ||
|
||
bufList := stream.Attachment.([]types.BufferInterface) | ||
_placeholder := &Placeholder{Placeholder: true, Num: len(bufList)} | ||
stream.WriteVal(_placeholder) | ||
stream.Attachment = append(bufList, bb) | ||
}, nil) | ||
|
||
jsoniter.RegisterTypeEncoderFunc("[]byte", func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. []byte doesn't seem to be working as expected, maybe there's something wrong with my testing? package main
import (
"fmt"
"github.com/zishang520/socket.io-go-parser/v2/parser"
)
func main() {
en := parser.NewEncoder()
id := uint64(1)
Attachments := uint64(1)
fmt.Println(en.Encode(&parser.Packet{
Type: parser.EVENT,
Nsp: "/a",
Data: []any{"any", []byte{1, 2, 3}},
Id: &id,
Attachments: &Attachments,
}))
} need: [51-/a,1["any",{"_placeholder":true,"num":0}] ╔╗╚] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kk fixed this up |
||
bb := types.NewBytesBuffer(nil) | ||
barr := ((*[]byte)(ptr)) | ||
bb.Write(*barr) | ||
|
||
bufList := stream.Attachment.([]types.BufferInterface) | ||
_placeholder := &Placeholder{Placeholder: true, Num: len(bufList)} | ||
stream.WriteVal(_placeholder) | ||
stream.Attachment = append(bufList, bb) | ||
}, nil) | ||
} | ||
|
||
// Replaces every io.Reader | []byte in packet with a numbered placeholder. | ||
func DeconstructPacket(packet *Packet) (pack *Packet, buffers []types.BufferInterface) { | ||
pack = packet | ||
pack.Data = _deconstructPacket(packet.Data, &buffers) | ||
|
||
// Run the serialization now, replacing any bytebuffers/[]byte found along the way with placeholders | ||
buf := &bytes.Buffer{} | ||
ns := jsoniter.NewStream(jsoniter.ConfigDefault, buf, buf.Cap()) | ||
ns.Attachment = buffers | ||
ns.WriteVal(pack.Data) | ||
buffers = ns.Attachment.([]types.BufferInterface) | ||
ns.Flush() | ||
pack.Data = buf.String() | ||
|
||
attachments := uint64(len(buffers)) | ||
pack.Attachments = &attachments // number of binary 'attachments' | ||
return pack, buffers | ||
} | ||
|
||
func _deconstructPacket(data any, buffers *[]types.BufferInterface) any { | ||
if data == nil { | ||
return nil | ||
} | ||
|
||
if IsBinary(data) { | ||
_placeholder := &Placeholder{Placeholder: true, Num: len(*buffers)} | ||
rdata := types.NewBytesBuffer(nil) | ||
switch tdata := data.(type) { | ||
case io.Reader: | ||
if c, ok := data.(io.Closer); ok { | ||
defer c.Close() | ||
} | ||
rdata.ReadFrom(tdata) | ||
case []byte: | ||
rdata.Write(tdata) | ||
} | ||
*buffers = append(*buffers, rdata) | ||
return _placeholder | ||
} | ||
|
||
switch tdata := data.(type) { | ||
case []any: | ||
newData := make([]any, 0, len(tdata)) | ||
for _, v := range tdata { | ||
newData = append(newData, _deconstructPacket(v, buffers)) | ||
} | ||
return newData | ||
case map[string]any: | ||
newData := map[string]any{} | ||
for k, v := range tdata { | ||
newData[k] = _deconstructPacket(v, buffers) | ||
} | ||
return newData | ||
default: | ||
return data | ||
} | ||
} | ||
|
||
// Reconstructs a binary packet from its placeholder packet and buffers | ||
func ReconstructPacket(packet *Packet, buffers []types.BufferInterface) (*Packet, error) { | ||
data, err := _reconstructPacket(packet.Data, &buffers) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,9 +53,9 @@ func _encodeData(data any) any { | |
newData[k] = _encodeData(v) | ||
} | ||
return newData | ||
default: | ||
return data | ||
} | ||
|
||
return data | ||
} | ||
|
||
// Encode packet as string. | ||
|
@@ -79,8 +79,13 @@ func (e *encoder) encodeAsString(packet *Packet) types.BufferInterface { | |
} | ||
// json data | ||
if nil != packet.Data { | ||
if b, err := json.Marshal(_encodeData(packet.Data)); err == nil { | ||
str.Write(b) | ||
if pds, is := packet.Data.(string); is { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This behavior seems inconsistent with nodejs socket.io-parser, which will cause unpredictable problems in other clients: package main
import (
"fmt"
"github.com/zishang520/socket.io-go-parser/v2/parser"
)
func main() {
en := parser.NewEncoder()
id := uint64(1)
Attachments := uint64(1)
fmt.Println(en.Encode(&parser.Packet{
Type: parser.EVENT,
Nsp: "/a",
Data: "any",
Id: &id,
Attachments: &Attachments,
}))
} output: [2/a,1any] test nodejs (latest):
output: [ '2/a,1"any"' ] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, wasn't aware you could use it that way. Adding a private field to store it instad... |
||
// Already serialized in the DeconstructPacket function | ||
str.WriteString(pds) | ||
} else { | ||
if b, err := json.Marshal(_encodeData(packet.Data)); err == nil { | ||
str.Write(b) | ||
} | ||
} | ||
} | ||
parser_log.Debug("encoded %v as %v", packet, str) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package parser | |
|
||
import ( | ||
"io" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/zishang520/engine.io-go-parser/types" | ||
|
@@ -30,12 +31,25 @@ func HasBinary(data any) bool { | |
return true | ||
} | ||
} | ||
return false | ||
case map[string]any: | ||
for _, v := range o { | ||
if HasBinary(v) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
dv := reflect.ValueOf(data) | ||
if dv.Kind() == reflect.Struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe consider adding reflect.Ptr support. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough! I wasn't sure how well updated this library was and whether you'd like the PR at all since it feels a little hacky to me, so wanted to get something basic out first. :D Will add this... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, given this feature, we can even do more, such as support for Array and Map. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done! |
||
for fi := range dv.NumField() { | ||
dfv := dv.Field(fi) | ||
if dfv.CanInterface() && HasBinary(dfv.Interface()) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
return IsBinary(data) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last update to the standard library was two years ago, and it seems to be out of maintenance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't love this fact, but it was the only high-performance json library I could find that supported passing context through each individual serialization versus having to do some horrible thing with finding pointers to all arrays in a first pass, storing them in a global lookup, and then using that to back-calculate the "current stream" from inside the custom marshal functions. It's well-regarded across the internet though, has 13k stars, etc., so I'm not that worried, but your call about how worried you are about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a rapidly developing network, stopping updates means many security risks.