github.com/erda-project/erda-infra@v1.0.10-0.20240327085753-f3a249292aeb/pkg/transport/http/encoding/encoding.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package encoding 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "mime" 22 "net/http" 23 "net/url" 24 "strings" 25 26 "github.com/erda-project/erda-infra/pkg/urlenc" 27 "google.golang.org/protobuf/encoding/protojson" 28 "google.golang.org/protobuf/proto" 29 ) 30 31 type notSupportMediaTypeErr struct { 32 text string 33 } 34 35 func (e notSupportMediaTypeErr) HTTPStatus() int { return http.StatusNotAcceptable } 36 func (e notSupportMediaTypeErr) Error() string { return e.text } 37 38 // DecodeRequest . 39 func DecodeRequest(r *http.Request, out interface{}) error { 40 if out == nil { 41 return nil 42 } 43 if bytesPtr, ok := out.(*[]byte); ok { 44 body, err := ioutil.ReadAll(r.Body) 45 if err != nil { 46 return err 47 } 48 *bytesPtr = body 49 return nil 50 } 51 contentType := r.Header.Get("Content-Type") 52 if len(contentType) <= 0 { 53 return nil 54 } 55 mtype, _, err := mime.ParseMediaType(contentType) 56 if err != nil { 57 return err 58 } 59 switch mtype { 60 case "application/protobuf", "application/x-protobuf": 61 if r.ContentLength <= 0 { 62 return nil 63 } 64 if msg, ok := out.(proto.Message); ok { 65 body, err := ioutil.ReadAll(r.Body) 66 if err != nil { 67 return err 68 } 69 if len(body) <= 0 { 70 return nil 71 } 72 return proto.Unmarshal(body, msg) 73 } 74 case "application/x-www-form-urlencoded", "multipart/form-data": 75 if un, ok := out.(urlenc.URLValuesUnmarshaler); ok { 76 err := r.ParseForm() 77 if err != nil { 78 return err 79 } 80 return un.UnmarshalURLValues("", r.Form) 81 } 82 default: 83 if r.ContentLength <= 0 { 84 return nil 85 } 86 if mtype == "application/json" || (strings.HasPrefix(mtype, "application/vnd.") && strings.HasSuffix(mtype, "+json")) { 87 if um, ok := out.(json.Unmarshaler); ok { 88 body, err := ioutil.ReadAll(r.Body) 89 if err != nil { 90 return err 91 } 92 return um.UnmarshalJSON(body) 93 } else if msg, ok := out.(proto.Message); ok { 94 body, err := ioutil.ReadAll(r.Body) 95 if err != nil { 96 return err 97 } 98 if len(body) <= 0 { 99 return nil 100 } 101 return protojson.Unmarshal(body, msg) 102 } 103 return json.NewDecoder(r.Body).Decode(out) 104 } 105 } 106 return notSupportMediaTypeErr{text: fmt.Sprintf("not support media type: %s", mtype)} 107 } 108 109 // EncodeResponse . 110 func EncodeResponse(w http.ResponseWriter, r *http.Request, out interface{}) error { 111 if out == nil { 112 return nil 113 } 114 accept := r.Header.Get("Accept") 115 var acceptAny bool 116 if len(accept) > 0 { 117 // TODO select MediaType of max q 118 for _, item := range strings.Split(accept, ",") { 119 mtype, _, err := mime.ParseMediaType(item) 120 if err != nil { 121 return err 122 } 123 if mtype == "*/*" { 124 acceptAny = true 125 continue 126 } 127 ok, err := encodeResponse(mtype, w, r, out) 128 if ok { 129 if err != nil { 130 return err 131 } 132 return nil 133 } 134 } 135 } else { 136 _, err := encodeResponse("application/json", w, r, out) 137 return err 138 } 139 if acceptAny { 140 contentType := r.Header.Get("Content-Type") 141 if len(contentType) > 0 { 142 mtype, _, err := mime.ParseMediaType(contentType) 143 if err != nil { 144 return err 145 } 146 ok, err := encodeResponse(mtype, w, r, out) 147 if ok { 148 if err != nil { 149 return err 150 } 151 return nil 152 } 153 } 154 _, err := encodeResponse("application/json", w, r, out) 155 return err 156 } 157 return notSupportMediaTypeErr{text: fmt.Sprintf("not support media type: %s", accept)} 158 } 159 160 func encodeResponse(mtype string, w http.ResponseWriter, r *http.Request, out interface{}) (bool, error) { 161 switch mtype { 162 case "application/protobuf", "application/x-protobuf": 163 if msg, ok := out.(proto.Message); ok { 164 byts, err := proto.Marshal(msg) 165 if err != nil { 166 return false, err 167 } 168 w.Header().Set("Content-Type", "application/protobuf") 169 _, err = w.Write(byts) 170 return true, err 171 } 172 case "application/x-www-form-urlencoded", "multipart/form-data": 173 if m, ok := out.(urlenc.URLValuesMarshaler); ok { 174 vals := make(url.Values) 175 w.Header().Set("Content-Type", "application/x-www-form-urlencoded") 176 return true, m.MarshalURLValues("", vals) 177 } 178 default: 179 if mtype == "application/json" || (strings.HasPrefix(mtype, "application/vnd.") && strings.HasSuffix(mtype, "+json")) { 180 if msg, ok := out.(proto.Message); ok { 181 byts, err := protojson.Marshal(msg) 182 if err != nil { 183 return false, err 184 } 185 w.Header().Set("Content-Type", "application/json") 186 _, err = w.Write(byts) 187 return true, err 188 } 189 w.Header().Set("Content-Type", "application/json") 190 return true, json.NewEncoder(w).Encode(out) 191 } 192 } 193 return false, nil 194 }