Skip to content
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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

deregtd
Copy link

@deregtd deregtd commented Jul 21, 2024

I've pulled in jsoniter to support passing a context into a json serialization, so we can stop walking and rewriting the entire passed structure to replace with placeholders. Instead, for the registered types, we replace them with placeholders inline as they're serialized, writing their attachments to a stream-context-passed attachments list, so it's multiple-simultaneous-encoding-safe. Now you can use structures instead of typeless maps for passing arraybuffers.

Example:

m := MyStruct{
	SomeString: "hi",
	BinaryData: types.NewBytesBuffer([]byte{1,2,3,4}),
}
if err := s.conn.Emit(EventId, m); err != nil {
...

I've pulled in jsoniter to support passing a context into a json serialization, so we can stop walking and rewriting the entire passed structure to replace with placeholders.  Instead, for the registered types, we replace them with placeholders inline as they're serialized, writing their attachments to a stream-context-passed attachments list, so it's multiple-simultaneous-encoding-safe.
@zishang520
Copy link
Owner

Thanks for your PR, I need to review this request.

Copy link
Owner

@zishang520 zishang520 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Struct support is a great feature.


jsoniter "github.com/json-iterator/go"
Copy link
Owner

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.

Copy link
Author

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.

Copy link
Owner

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.

return false
}
dv := reflect.ValueOf(data)
if dv.Kind() == reflect.Struct {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe consider adding reflect.Ptr support.

Copy link
Author

Choose a reason for hiding this comment

The 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...

Copy link
Owner

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The 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:
test golang:

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):

const {
    Encoder,
    PacketType
} = require("socket.io-parser");
let en = new Encoder;

console.log(en.encode({
    type: PacketType.EVENT,
    nsp: "/a",
    data: "any",
    id: 1,
    attachments: 1,
}))

output: [ '2/a,1"any"' ]

Copy link
Author

Choose a reason for hiding this comment

The 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...

Copy link
Owner

@zishang520 zishang520 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirm again, pls.

parser/binary.go Outdated
stream.Attachment = append(bufList, bb)
}, nil)

jsoniter.RegisterTypeEncoderFunc("[]byte", func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
Copy link
Owner

Choose a reason for hiding this comment

The 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}] ╔╗╚]
output: [50-/a,1["any","AQID"]]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kk fixed this up

@zishang520
Copy link
Owner

I plan to improve the unit testing in the short term (:

@@ -79,9 +79,9 @@ func (e *encoder) encodeAsString(packet *Packet) types.BufferInterface {
}
// json data
if nil != packet.Data {
if pds, is := packet.Data.(string); is {
if packet.preSerializedData != "" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to run more test cases to ensure that this request is OK. It may take longer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no rush. I've already taken the encoder from this branch and I'm using it in my project that way via the config.Encoder.

@deregtd
Copy link
Author

deregtd commented Jul 24, 2024

BTW, just to be clear, feel free to hack this PR up. I'm not attached to this implementation, it just worked for my use case and looked reasonably generalized. :)

@zishang520
Copy link
Owner

Due to the reason of github.com/json-iterator/go, I still need to investigate further. This PR will not be merged for the time being. Wait for me to evaluate the results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants