github.com/viant/toolbox@v0.34.5/bridge/http_bridge.go (about) 1 package bridge 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "github.com/viant/toolbox" 9 "io" 10 "io/ioutil" 11 "log" 12 "net" 13 "net/http" 14 "net/http/httputil" 15 "net/url" 16 "os" 17 "path" 18 "sync" 19 "time" 20 "unicode" 21 "unicode/utf8" 22 ) 23 24 //HttpBridgeConfig represent http bridge config 25 type HttpBridgeEndpointConfig struct { 26 Port string 27 ReadTimeoutMs int 28 WriteTimeoutMs int 29 MaxHeaderBytes int 30 } 31 32 //HttpBridgeProxyRoute represent http proxy route 33 type HttpBridgeProxyRoute struct { 34 Pattern string 35 TargetURL *url.URL 36 ResponseModifier func(*http.Response) error 37 Listener func(request *http.Request, response *http.Response) 38 } 39 40 //HttpBridgeProxyConfig represent proxy config 41 type HttpBridgeProxyConfig struct { 42 MaxIdleConnections int 43 RequestTimeoutMs int 44 KeepAliveTimeMs int 45 TLSHandshakeTimeoutMs int 46 BufferPoolSize int 47 BufferSize int 48 } 49 50 //HttpBridgeConfig represents HttpBridgeConfig config 51 type HttpBridgeConfig struct { 52 Endpoint *HttpBridgeEndpointConfig 53 Proxy *HttpBridgeProxyConfig 54 Routes []*HttpBridgeProxyRoute 55 } 56 57 //ProxyHandlerFactory proxy handler factory 58 type HttpBridgeProxyHandlerFactory func(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) 59 60 //HttpBridge represents http bridge 61 type HttpBridge struct { 62 Config *HttpBridgeConfig 63 Server *http.Server 64 Handlers map[string]http.Handler 65 } 66 67 //ListenAndServe start http endpoint 68 func (r *HttpBridge) ListenAndServe() error { 69 return r.Server.ListenAndServe() 70 } 71 72 //ListenAndServe start http endpoint on secure port 73 func (r *HttpBridge) ListenAndServeTLS(certFile, keyFile string) error { 74 return r.Server.ListenAndServeTLS(certFile, keyFile) 75 } 76 77 //NewHttpBridge creates a new instance of NewHttpBridge 78 func NewHttpBridge(config *HttpBridgeConfig, factory HttpBridgeProxyHandlerFactory) (*HttpBridge, error) { 79 mux := http.NewServeMux() 80 var handlers = make(map[string]http.Handler) 81 for _, route := range config.Routes { 82 handler, err := factory(config.Proxy, route) 83 if err != nil { 84 return nil, err 85 } 86 mux.Handle(route.Pattern, handler) 87 handlers[route.Pattern] = handler 88 } 89 server := &http.Server{ 90 Addr: ":" + config.Endpoint.Port, 91 Handler: mux, 92 ReadTimeout: time.Millisecond * time.Duration(config.Endpoint.ReadTimeoutMs), 93 WriteTimeout: time.Millisecond * time.Duration(config.Endpoint.WriteTimeoutMs), 94 MaxHeaderBytes: config.Endpoint.MaxHeaderBytes, 95 } 96 return &HttpBridge{ 97 Server: server, 98 Config: config, 99 Handlers: handlers, 100 }, nil 101 } 102 103 type handlerWrapper struct { 104 Handler http.Handler 105 } 106 107 func (h *handlerWrapper) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 108 h.Handler.ServeHTTP(writer, request) 109 } 110 111 //NewProxyHandler creates a new proxy handler 112 func NewProxyHandler(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) { 113 roundTripper := &http.Transport{ 114 Proxy: http.ProxyFromEnvironment, 115 Dial: (&net.Dialer{ 116 Timeout: time.Duration(proxyConfig.RequestTimeoutMs) * time.Millisecond, 117 KeepAlive: time.Duration(proxyConfig.KeepAliveTimeMs) * time.Millisecond, 118 }).Dial, 119 TLSHandshakeTimeout: time.Duration(proxyConfig.TLSHandshakeTimeoutMs) * time.Millisecond, 120 MaxIdleConnsPerHost: proxyConfig.MaxIdleConnections, 121 } 122 123 var director func(*http.Request) 124 125 if route.TargetURL != nil { 126 director = func(request *http.Request) { 127 request.URL.Scheme = route.TargetURL.Scheme 128 request.URL.Host = route.TargetURL.Host 129 } 130 } 131 reverseProxy := &httputil.ReverseProxy{ 132 Transport: roundTripper, 133 BufferPool: toolbox.NewBytesBufferPool(proxyConfig.BufferPoolSize, proxyConfig.BufferSize), 134 ModifyResponse: route.ResponseModifier, 135 Director: director, 136 } 137 var handler http.Handler = &handlerWrapper{reverseProxy} 138 return handler, nil 139 } 140 141 //HTTPTrip represents recorded round trip. 142 type HttpTrip struct { 143 responseWriter http.ResponseWriter 144 Request *http.Request 145 responseBody *bytes.Buffer 146 responseStatusCode int 147 } 148 149 func (w *HttpTrip) Response() *http.Response { 150 return &http.Response{ 151 Request: w.Request, 152 StatusCode: w.responseStatusCode, 153 Header: w.responseWriter.Header(), 154 Body: ioutil.NopCloser(bytes.NewReader(w.responseBody.Bytes())), 155 } 156 } 157 158 func (w *HttpTrip) Write(b []byte) (int, error) { 159 w.responseBody.Write(b) 160 return w.responseWriter.Write(b) 161 } 162 163 func (w *HttpTrip) Header() http.Header { 164 return w.responseWriter.Header() 165 } 166 167 func (w *HttpTrip) WriteHeader(status int) { 168 w.responseStatusCode = status 169 w.responseWriter.WriteHeader(status) 170 } 171 172 func (w *HttpTrip) Flush() { 173 if flusher, ok := w.responseWriter.(http.Flusher); ok { 174 flusher.Flush() 175 } 176 } 177 178 func (w *HttpTrip) CloseNotify() <-chan bool { 179 if closer, ok := w.responseWriter.(http.CloseNotifier); ok { 180 return closer.CloseNotify() 181 } 182 return make(chan bool, 1) 183 } 184 185 //ListeningTripHandler represents endpoint recording handler 186 type ListeningTripHandler struct { 187 handler http.Handler 188 pool httputil.BufferPool 189 listener func(request *http.Request, response *http.Response) 190 roundTripsMutex *sync.RWMutex 191 } 192 193 func (h *ListeningTripHandler) Notify(roundTrip *HttpTrip) { 194 if h.listener != nil { 195 h.listener(roundTrip.Request, roundTrip.Response()) 196 } 197 } 198 199 //drainBody reads all of b to memory and then returns two equivalent (modified version from httputil) 200 func (h ListeningTripHandler) drainBody(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser, error) { 201 if reader == http.NoBody { 202 return http.NoBody, http.NoBody, nil 203 } 204 var buf = new(bytes.Buffer) 205 toolbox.CopyWithBufferPool(reader, buf, h.pool) 206 return ioutil.NopCloser(bytes.NewReader(buf.Bytes())), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil 207 } 208 209 func (h ListeningTripHandler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { 210 var err error 211 var originalRequest = request.WithContext(request.Context()) 212 if request.ContentLength > 0 { 213 request.Body, originalRequest.Body, err = h.drainBody(request.Body) 214 if err != nil { 215 log.Printf("failed to serve request :%v due to %v\n", request, err) 216 return 217 } 218 } 219 var recordedRoundTrip = &HttpTrip{ 220 responseWriter: responseWriter, 221 Request: originalRequest, 222 responseBody: new(bytes.Buffer), 223 } 224 responseWriter = http.ResponseWriter(recordedRoundTrip) 225 defer h.Notify(recordedRoundTrip) 226 h.handler.ServeHTTP(responseWriter, request) 227 } 228 229 func NewListeningHandler(handler http.Handler, bufferPoolSize, bufferSize int, listener func(request *http.Request, response *http.Response)) *ListeningTripHandler { 230 var result = &ListeningTripHandler{ 231 handler: handler, 232 listener: listener, 233 pool: toolbox.NewBytesBufferPool(bufferPoolSize, bufferSize), 234 roundTripsMutex: &sync.RWMutex{}, 235 } 236 return result 237 } 238 239 func NewProxyRecordingHandler(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) { 240 handler, err := NewProxyHandler(proxyConfig, route) 241 if err != nil { 242 return nil, err 243 } 244 response := NewListeningHandler(handler, proxyConfig.BufferPoolSize, proxyConfig.BufferSize, route.Listener) 245 return response, nil 246 } 247 248 func AsListeningTripHandler(handler http.Handler) *ListeningTripHandler { 249 if result, ok := handler.(*ListeningTripHandler); ok { 250 return result 251 } 252 return nil 253 } 254 255 //HttpRequest represents JSON serializable http request 256 type HttpRequest struct { 257 Method string `json:",omitempty"` 258 URL string `json:",omitempty"` 259 Header http.Header `json:",omitempty"` 260 Body string `json:",omitempty"` 261 ThinkTimeMs int `json:",omitempty"` 262 } 263 264 //NewHTTPRequest create a new instance of http request 265 func NewHTTPRequest(method, url, body string, header http.Header) *HttpRequest { 266 return &HttpRequest{ 267 Method: method, 268 URL: url, 269 Body: body, 270 Header: header, 271 } 272 } 273 274 //HttpResponse represents JSON serializable http response 275 type HttpResponse struct { 276 Code int 277 Header http.Header `json:",omitempty"` 278 Body string `json:",omitempty"` 279 } 280 281 func ReaderAsText(reader io.Reader) string { 282 if reader == nil { 283 return "" 284 } 285 body, err := ioutil.ReadAll(reader) 286 if err != nil { 287 log.Fatal(err) 288 } 289 if isBinary(body) { 290 buf := new(bytes.Buffer) 291 encoder := base64.NewEncoder(base64.StdEncoding, buf) 292 encoder.Write(body) 293 encoder.Close() 294 return fmt.Sprintf("base64:%v", string(buf.Bytes())) 295 296 } else if len(body) > 0 { 297 return fmt.Sprintf("text:%v", string(body)) 298 } 299 return "" 300 } 301 302 func isBinary(input []byte) bool { 303 for i, w := 0, 0; i < len(input); i += w { 304 runeValue, width := utf8.DecodeRune(input[i:]) 305 if unicode.IsControl(runeValue) { 306 return true 307 } 308 w = width 309 } 310 return false 311 } 312 313 func writeData(filename string, source interface{}, printStrOut bool) error { 314 if toolbox.FileExists(filename) { 315 os.Remove(filename) 316 } 317 318 logfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666) 319 if err != nil { 320 log.Fatalf("error opening file: %v, %v", err, filename) 321 } 322 defer logfile.Close() 323 324 buf, err := json.MarshalIndent(source, "", "\t") 325 if err != nil { 326 return err 327 } 328 _, err = logfile.Write(buf) 329 330 if printStrOut { 331 fmt.Printf("%v: %v\n", filename, string(buf)) 332 } 333 334 return err 335 } 336 337 //HttpFileRecorder returns http route listener that will record request response to the passed in directory 338 func HttpFileRecorder(directory string, printStdOut bool) func(request *http.Request, response *http.Response) { 339 tripCounter := 0 340 341 err := toolbox.CreateDirIfNotExist(directory) 342 if err != nil { 343 fmt.Printf("failed to create directory%v %v\n, ", err, directory) 344 } 345 return func(request *http.Request, response *http.Response) { 346 var body string 347 if request.Body != nil { 348 body = ReaderAsText(request.Body) 349 } 350 httpRequest := &HttpRequest{ 351 Method: request.Method, 352 URL: request.URL.String(), 353 Header: request.Header, 354 Body: body, 355 } 356 357 err = writeData(path.Join(directory, fmt.Sprintf("%T-%v.json", *httpRequest, tripCounter)), httpRequest, printStdOut) 358 if err != nil { 359 fmt.Printf("failed to write request %v %v\n, ", err, request) 360 } 361 362 body = ReaderAsText(response.Body) 363 request.Body = nil 364 httpResponse := &HttpResponse{ 365 Code: response.StatusCode, 366 Header: response.Header, 367 Body: body, 368 } 369 370 err = writeData(path.Join(directory, fmt.Sprintf("%T-%v.json", *httpResponse, tripCounter)), httpResponse, printStdOut) 371 if err != nil { 372 fmt.Printf("failed to write response %v %v\n, ", err, response) 373 } 374 375 tripCounter++ 376 } 377 378 }