github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/transports/response.go (about) 1 /* 2 * Copyright 2023 Wang Min Xiang 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package transports 19 20 import ( 21 "bufio" 22 "fmt" 23 "github.com/aacfactory/errors" 24 "github.com/aacfactory/fns/commons/bytex" 25 "github.com/aacfactory/fns/commons/objects" 26 "github.com/aacfactory/fns/context" 27 "github.com/valyala/bytebufferpool" 28 "io" 29 "net" 30 "net/http" 31 "strconv" 32 "sync" 33 "time" 34 ) 35 36 type ResponseWriter interface { 37 context.Context 38 Status() int 39 SetStatus(status int) 40 SetCookie(cookie *Cookie) 41 Header() Header 42 Succeed(v any) 43 Failed(cause error) 44 Write(body []byte) (int, error) 45 Body() []byte 46 BodyLen() int 47 ResetBody() 48 Hijack(func(ctx context.Context, conn net.Conn, rw *bufio.ReadWriter) (err error)) (async bool, err error) 49 Hijacked() bool 50 WriteTimeout() time.Duration 51 WriteDeadline() time.Time 52 } 53 54 type WriteBuffer interface { 55 io.Writer 56 Bytes() []byte 57 } 58 59 var ( 60 responseWriterPool = sync.Pool{} 61 ) 62 63 func AcquireResultResponseWriter(timeout time.Duration, contentType []byte) *ResultResponseWriter { 64 deadline := time.Time{} 65 if timeout > 0 { 66 deadline = deadline.Add(timeout) 67 } 68 buf := bytebufferpool.Get() 69 cached := responseWriterPool.Get() 70 if cached == nil { 71 return &ResultResponseWriter{ 72 contentType: contentType, 73 status: 0, 74 timeout: timeout, 75 deadline: deadline, 76 header: NewHeader(), 77 body: buf, 78 } 79 } 80 r := cached.(*ResultResponseWriter) 81 r.contentType = contentType 82 r.body = buf 83 r.timeout = timeout 84 r.deadline = deadline 85 return r 86 } 87 88 func ReleaseResultResponseWriter(w *ResultResponseWriter) { 89 bytebufferpool.Put(w.body) 90 w.header.Reset() 91 w.contentType = nil 92 w.body = nil 93 w.status = 0 94 w.timeout = 0 95 w.deadline = time.Time{} 96 responseWriterPool.Put(w) 97 } 98 99 type ResultResponseWriter struct { 100 contentType []byte 101 status int 102 timeout time.Duration 103 deadline time.Time 104 header Header 105 body *bytebufferpool.ByteBuffer 106 } 107 108 func (w *ResultResponseWriter) Status() int { 109 return w.status 110 } 111 112 func (w *ResultResponseWriter) SetStatus(status int) { 113 w.status = status 114 } 115 116 func (w *ResultResponseWriter) Header() (h Header) { 117 h = w.header 118 return 119 } 120 121 func (w *ResultResponseWriter) Body() []byte { 122 return w.body.Bytes() 123 } 124 125 func (w *ResultResponseWriter) BodyLen() int { 126 return w.body.Len() 127 } 128 129 func (w *ResultResponseWriter) ResetBody() { 130 w.body.Reset() 131 } 132 133 func (w *ResultResponseWriter) Succeed(v any) { 134 if v == nil { 135 w.status = http.StatusOK 136 return 137 } 138 obj, isObject := v.(objects.Object) 139 if isObject { 140 v = obj.Value() 141 } 142 if v == nil { 143 w.status = http.StatusOK 144 return 145 } 146 encoder, contentType := GetMarshaler(w.contentType) 147 p, err := encoder(v) 148 if err != nil { 149 w.Failed(errors.Warning("fns: transport write succeed result failed").WithCause(err)) 150 return 151 } 152 w.status = http.StatusOK 153 w.header.Set(ContentTypeHeaderName, contentType) 154 _, _ = w.Write(p) 155 return 156 } 157 158 func (w *ResultResponseWriter) Failed(cause error) { 159 if cause == nil { 160 cause = errors.Warning("fns: error is lost") 161 } 162 err := errors.Wrap(cause) 163 encoder, contentType := GetMarshaler(w.contentType) 164 body, bodyErr := encoder(err) 165 if bodyErr != nil { 166 w.status = 666 167 w.header.Set(ContentTypeHeaderName, ContentTypeTextHeaderValue) 168 _, _ = w.Write([]byte(fmt.Sprintf("%+v", bodyErr))) 169 return 170 } 171 w.status = err.Code() 172 w.header.Set(ContentTypeHeaderName, contentType) 173 _, _ = w.Write(body) 174 return 175 } 176 177 func (w *ResultResponseWriter) Write(body []byte) (int, error) { 178 bodyLen := len(body) 179 w.Header().Set(ContentLengthHeaderName, bytex.FromString(strconv.Itoa(bodyLen))) 180 if bodyLen > 0 { 181 return w.body.Write(body) 182 } 183 return 0, nil 184 } 185 186 func (w *ResultResponseWriter) WriteTimeout() time.Duration { 187 return w.timeout 188 } 189 190 func (w *ResultResponseWriter) WriteDeadline() time.Time { 191 return w.deadline 192 } 193 194 var ( 195 responseContextKey = []byte("@fns:context:transports:response") 196 ) 197 198 func WithResponse(ctx context.Context, w ResponseWriter) context.Context { 199 ctx.SetLocalValue(responseContextKey, w) 200 return ctx 201 } 202 203 func TryLoadResponseWriter(ctx context.Context) (ResponseWriter, bool) { 204 w, ok := ctx.(ResponseWriter) 205 if ok { 206 return w, ok 207 } 208 v := ctx.LocalValue(responseContextKey) 209 if v == nil { 210 return nil, false 211 } 212 w, ok = v.(ResponseWriter) 213 return w, ok 214 } 215 216 func LoadResponseWriter(ctx context.Context) ResponseWriter { 217 w, ok := TryLoadResponseWriter(ctx) 218 if ok { 219 return w 220 } 221 return nil 222 } 223 224 func TryLoadResponseHeader(ctx context.Context) (Header, bool) { 225 w, ok := TryLoadResponseWriter(ctx) 226 if !ok { 227 return nil, false 228 } 229 return w.Header(), ok 230 }