trpc-group / trpc-go

A pluggable, high-performance RPC framework written in golang
Other
742 stars 85 forks source link

Why FormDataSerialization.Marshal use JSONPBSerialization.Marshal to implement? #147

Closed eipi10ydz closed 5 months ago

eipi10ydz commented 6 months ago

Preliminary Research

Question

I read the code about FormDataSerialization.Marshal and find that the Marshal member function of FormDataSerialization uses JSONPBSerialization.Marshal to implement.

From my point of view, the Marshal member function of FormDataSerialization needs to implement HTTP body serialization of Content-Type: multipart/form-data but not Content-Type: application/json.

This implementation can be confusing to users.

Additional Information

Related codes are shown below.

var (
    // tagJSON uses same tag with json.
    tagJSON = "json"
    // FormDataMarshalType the serialization method of the response data,
    // default is json serialization.
    FormDataMarshalType = codec.SerializationTypeJSON
)

func init() {
    codec.RegisterSerializer(
        codec.SerializationTypeFormData,
        NewFormDataSerialization(tagJSON),
    )
}

// Marshal serializes.
func (j *FormDataSerialization) Marshal(body interface{}) ([]byte, error) {
    serializer := codec.GetSerializer(FormDataMarshalType)
    if serializer == nil {
        return nil, errors.New("empty json serializer")
    }
    return serializer.Marshal(body)
}
WineChord commented 6 months ago

The trpc-go HTTP protocol server supports three main types:

  1. HTTP RPC
  2. Standard HTTP server
  3. RESTful style using proto annotations

The http package primarily caters to HTTP RPC, which provides a basic mapping between HTTP content and RPC's request/response. This works well for 'application/json' and 'application/x-www-form-urlencoded', where we can use JSON serialization to convert between the HTTP body and the request/response structure for service stubs.

However, this approach falls short for HTTP streaming or scenarios like 'multipart/form-data', where raw binary data is difficult to map to structures.

Let's consider 'multipart/form-data' as an example. The reading and processing of multipart body are handled within the framework's code, and it's unrelated to the codec.SerializationTypeFormData type. This type only affects the Form part (like key1=value1&key2=value2). That's why the Unmarshal implementation for codec.FormDataSerialization behaves similarly to 'application/x-www-form-urlencoded'. Service stub requests are unmarshaled from these form values. The actual multipart/formdata is manually accessed by thttp.Head(ctx) to retrieve the parsed Request.MultipartForm.

For the response of a stub, a serialization type should also be provided. The developers chose to use JSON. The reason is that the serialization type in the context's message is the same for both request and response. So, we have codec.FormDataSerialization for both, but the response for 'multipart/formdata' usually differs in content type. Even though there's logic in the server codec's encode to set the expected content type (JSON), it's too late as encoding occurs after serialization. Therefore, the developers decided to provide a different implementation for codec.FormDataSerialization's Marshal.