github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/httptransport/httpx/httpx_response.go (about) 1 package httpx 2 3 import ( 4 "context" 5 "io" 6 "mime" 7 "net/http" 8 "net/textproto" 9 "net/url" 10 11 "github.com/machinefi/w3bstream/pkg/depends/kit/kit" 12 "github.com/machinefi/w3bstream/pkg/depends/kit/statusx" 13 ) 14 15 type Encode func(context.Context, io.Writer, interface{}) error 16 17 type ResolveEncode func(*Response) (Encode, error) 18 19 type ResponseWrapper func(v interface{}) *Response 20 21 func Compose(rws ...ResponseWrapper) ResponseWrapper { 22 return func(v interface{}) *Response { 23 rsp := ResponseFrom(v) 24 for i := len(rws) - 1; i >= 0; i-- { 25 rsp = rws[i](rsp) 26 } 27 return rsp 28 } 29 } 30 31 func WrapStatusCode(statusCode int) ResponseWrapper { 32 return func(v interface{}) *Response { 33 rsp := ResponseFrom(v) 34 rsp.StatusCode = statusCode 35 return rsp 36 } 37 } 38 39 func WrapCookies(cookies ...*http.Cookie) ResponseWrapper { 40 return func(v interface{}) *Response { 41 rsp := ResponseFrom(v) 42 rsp.Cookies = cookies 43 return rsp 44 } 45 } 46 47 func WrapSchema(s interface{}) ResponseWrapper { 48 return func(v interface{}) *Response { 49 rsp := ResponseFrom(v) 50 return rsp 51 } 52 } 53 54 func WrapContentType(ct string) ResponseWrapper { 55 return func(v interface{}) *Response { 56 rsp := ResponseFrom(v) 57 rsp.ContentType = ct 58 return rsp 59 } 60 } 61 62 func WrapMeta(metas ...kit.Metadata) ResponseWrapper { 63 return func(v interface{}) *Response { 64 rsp := ResponseFrom(v) 65 rsp.Meta = kit.FromMetas(metas...) 66 return rsp 67 } 68 } 69 70 func Metadata(k string, vs ...string) kit.Metadata { 71 return kit.Metadata{k: vs} 72 } 73 74 func ResponseFrom(v interface{}) *Response { 75 if r, ok := v.(*Response); ok { 76 return r 77 } 78 79 rsp := &Response{} 80 81 if redirectDescriber, ok := v.(RedirectDescriber); ok { 82 rsp.Location = redirectDescriber.Location() 83 rsp.StatusCode = redirectDescriber.StatusCode() 84 return rsp 85 } 86 87 if e, ok := v.(error); ok { 88 if e != nil { 89 se, ok := statusx.IsStatusErr(e) 90 if !ok { 91 if e == context.Canceled { 92 // https://httpstatuses.com/499 93 se = statusx.Wrap(e, 499, "ContextCanceled") 94 } else { 95 se = statusx.Wrap(e, http.StatusInternalServerError, "UnknownError") 96 } 97 } 98 v = se 99 } 100 } 101 102 rsp.Value = v 103 104 if with, ok := v.(kit.MetadataCarrier); ok { 105 rsp.Meta = with.Meta() 106 } 107 108 if with, ok := v.(WithCookies); ok { 109 rsp.Cookies = with.Cookies() 110 } 111 112 if with, ok := v.(WithContentType); ok { 113 rsp.ContentType = with.ContentType() 114 } 115 116 if with, ok := v.(WithStatusCode); ok { 117 rsp.StatusCode = with.StatusCode() 118 } 119 120 return rsp 121 } 122 123 type Response struct { 124 Value interface{} // Value of body 125 Meta kit.Metadata 126 Cookies []*http.Cookie 127 Location *url.URL 128 ContentType string 129 StatusCode int 130 } 131 132 func (r *Response) Unwrap() error { 133 if err, ok := r.Value.(error); ok { 134 return err 135 } 136 return nil 137 } 138 139 func (r *Response) Error() string { 140 if err := r.Unwrap(); err != nil { 141 return err.Error() 142 } 143 return "response error" 144 } 145 146 func (r *Response) WriteTo(rw http.ResponseWriter, req *http.Request, resolve ResolveEncode) error { 147 defer func() { r.Value = nil }() 148 if upgrader, ok := r.Value.(Upgrader); ok { 149 return upgrader.Upgrade(rw, req) 150 } 151 if r.StatusCode == 0 { 152 if r.Value == nil { 153 r.StatusCode = http.StatusNoContent 154 } else { 155 if req.Method == http.MethodPost { 156 r.StatusCode = http.StatusCreated 157 } else { 158 r.StatusCode = http.StatusOK 159 } 160 } 161 } 162 if r.Meta != nil { 163 header := rw.Header() 164 for k, vs := range r.Meta { 165 header[textproto.CanonicalMIMEHeaderKey(k)] = vs 166 } 167 } 168 if r.Cookies != nil { 169 for _, cookie := range r.Cookies { 170 if cookie != nil { 171 http.SetCookie(rw, cookie) 172 } 173 } 174 } 175 if r.Location != nil { 176 http.Redirect(rw, req, r.Location.String(), r.StatusCode) 177 return nil 178 } 179 if r.StatusCode == http.StatusNoContent { 180 rw.WriteHeader(r.StatusCode) 181 return nil 182 } 183 if r.ContentType != "" { 184 rw.Header().Set(HeaderContentType, r.ContentType) 185 } 186 switch v := r.Value.(type) { 187 case kit.Result: 188 rw.WriteHeader(r.StatusCode) 189 _, err := v.Into(rw) 190 return err 191 case io.Reader: 192 rw.WriteHeader(r.StatusCode) 193 defer func() { 194 if c, ok := v.(io.Closer); ok { 195 c.Close() 196 } 197 }() 198 _, err := io.Copy(rw, v) 199 return err 200 default: 201 enc, err := resolve(r) 202 if err != nil { 203 return err 204 } 205 return enc( 206 ContextWithStatusCode(req.Context(), r.StatusCode), 207 rw, r.Value, 208 ) 209 } 210 } 211 212 type ResponseWriteError interface { 213 WriteError(err error) (int, error) 214 } 215 216 type Upgrader interface { 217 Upgrade(http.ResponseWriter, *http.Request) error 218 } 219 220 func MaybeWriteHeader(ctx context.Context, w io.Writer, ct string, param map[string]string) { 221 if rw, ok := w.(WithHeader); ok { 222 if len(param) == 0 { 223 rw.Header().Set(HeaderContentType, ct) 224 } else { 225 rw.Header().Set( 226 HeaderContentType, 227 mime.FormatMediaType(ct, param), 228 ) 229 } 230 } 231 if rw, ok := w.(http.ResponseWriter); ok { 232 rw.WriteHeader(StatusCodeFromContext(ctx)) 233 } 234 }