blob: 8dd5c24db4279ec88e4df9a1ebac452af840e96b [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001package runtime
2
3import (
4 "errors"
khenaidoo26721882021-08-11 17:42:52 -04005 "mime"
khenaidoo59ce9dd2019-11-11 13:05:32 -05006 "net/http"
khenaidoo26721882021-08-11 17:42:52 -04007
8 "google.golang.org/grpc/grpclog"
khenaidoo59ce9dd2019-11-11 13:05:32 -05009)
10
11// MIMEWildcard is the fallback MIME type used for requests which do not match
12// a registered MIME type.
13const MIMEWildcard = "*"
14
15var (
16 acceptHeader = http.CanonicalHeaderKey("Accept")
17 contentTypeHeader = http.CanonicalHeaderKey("Content-Type")
18
19 defaultMarshaler = &JSONPb{OrigName: true}
20)
21
22// MarshalerForRequest returns the inbound/outbound marshalers for this request.
23// It checks the registry on the ServeMux for the MIME type set by the Content-Type header.
24// If it isn't set (or the request Content-Type is empty), checks for "*".
25// If there are multiple Content-Type headers set, choose the first one that it can
26// exactly match in the registry.
27// Otherwise, it follows the above logic for "*"/InboundMarshaler/OutboundMarshaler.
28func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, outbound Marshaler) {
29 for _, acceptVal := range r.Header[acceptHeader] {
30 if m, ok := mux.marshalers.mimeMap[acceptVal]; ok {
31 outbound = m
32 break
33 }
34 }
35
36 for _, contentTypeVal := range r.Header[contentTypeHeader] {
khenaidoo26721882021-08-11 17:42:52 -040037 contentType, _, err := mime.ParseMediaType(contentTypeVal)
38 if err != nil {
39 grpclog.Infof("Failed to parse Content-Type %s: %v", contentTypeVal, err)
40 continue
41 }
42 if m, ok := mux.marshalers.mimeMap[contentType]; ok {
khenaidoo59ce9dd2019-11-11 13:05:32 -050043 inbound = m
44 break
45 }
46 }
47
48 if inbound == nil {
49 inbound = mux.marshalers.mimeMap[MIMEWildcard]
50 }
51 if outbound == nil {
52 outbound = inbound
53 }
54
55 return inbound, outbound
56}
57
58// marshalerRegistry is a mapping from MIME types to Marshalers.
59type marshalerRegistry struct {
60 mimeMap map[string]Marshaler
61}
62
63// add adds a marshaler for a case-sensitive MIME type string ("*" to match any
64// MIME type).
65func (m marshalerRegistry) add(mime string, marshaler Marshaler) error {
66 if len(mime) == 0 {
67 return errors.New("empty MIME type")
68 }
69
70 m.mimeMap[mime] = marshaler
71
72 return nil
73}
74
75// makeMarshalerMIMERegistry returns a new registry of marshalers.
76// It allows for a mapping of case-sensitive Content-Type MIME type string to runtime.Marshaler interfaces.
77//
78// For example, you could allow the client to specify the use of the runtime.JSONPb marshaler
79// with a "application/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler
80// with a "application/json" Content-Type.
81// "*" can be used to match any Content-Type.
82// This can be attached to a ServerMux with the marshaler option.
83func makeMarshalerMIMERegistry() marshalerRegistry {
84 return marshalerRegistry{
85 mimeMap: map[string]Marshaler{
86 MIMEWildcard: defaultMarshaler,
87 },
88 }
89}
90
91// WithMarshalerOption returns a ServeMuxOption which associates inbound and outbound
92// Marshalers to a MIME type in mux.
93func WithMarshalerOption(mime string, marshaler Marshaler) ServeMuxOption {
94 return func(mux *ServeMux) {
95 if err := mux.marshalers.add(mime, marshaler); err != nil {
96 panic(err)
97 }
98 }
99}