@@ -9,8 +9,10 @@ import (
99 "io"
1010 "net/http"
1111 "net/url"
12+ "os"
1213 "runtime"
1314 "slices"
15+ "strconv"
1416 "strings"
1517 "sync"
1618 "time"
@@ -21,10 +23,11 @@ import (
2123)
2224
2325type Client struct {
24- ServerURL string
25- APIToken string
26- AccountID string
27- Hostname string
26+ ServerURL string
27+ APIToken string
28+ AccountID string
29+ Hostname string
30+ TLSInsecure bool
2831}
2932
3033type ConnectResult struct {
@@ -59,8 +62,10 @@ type managedStream struct {
5962
6063var heartbeatInterval = 15 * time .Second
6164
65+ const httpBodyChunkSize = 16 << 10
66+
6267func (c Client ) Connect (ctx context.Context ) (ConnectResult , * ws.Conn , error ) {
63- conn , _ , err := ws .Dial (ctx , c .ServerURL , nil )
68+ conn , _ , err := ws .Dial (ctx , c .ServerURL , c . dialOptions () )
6469 if err != nil {
6570 return ConnectResult {}, nil , fmt .Errorf ("dial websocket: %w" , err )
6671 }
@@ -86,7 +91,7 @@ func (c Client) Connect(ctx context.Context) (ConnectResult, *ws.Conn, error) {
8691}
8792
8893func (c Client ) Resume (ctx context.Context , sessionID , sessionToken string ) (ConnectResult , * ws.Conn , error ) {
89- conn , _ , err := ws .Dial (ctx , c .ServerURL , nil )
94+ conn , _ , err := ws .Dial (ctx , c .ServerURL , c . dialOptions () )
9095 if err != nil {
9196 return ConnectResult {}, nil , fmt .Errorf ("dial websocket: %w" , err )
9297 }
@@ -107,6 +112,14 @@ func (c Client) Resume(ctx context.Context, sessionID, sessionToken string) (Con
107112 return result , conn , nil
108113}
109114
115+ func (c Client ) dialOptions () * ws.DialOptions {
116+ if ! c .TLSInsecure {
117+ return nil
118+ }
119+ transport := & http.Transport {TLSClientConfig : & tls.Config {InsecureSkipVerify : true }}
120+ return & ws.DialOptions {HTTPClient : & http.Client {Transport : transport }}
121+ }
122+
110123func (c Client ) RegisterRoute (ctx context.Context , conn * ws.Conn , subdomain string , target map [string ]interface {}) (protocol.RouteRegisteredPayload , error ) {
111124 frame := protocol .NewEnvelope (protocol .TypeRouteRegister , protocol .ControlStreamID , protocol.RouteRegisterPayload {
112125 RequestedSubdomain : subdomain ,
@@ -515,6 +528,9 @@ func proxyRequest(ctx context.Context, httpClient *http.Client, manager *streamM
515528 if err != nil {
516529 return err
517530 }
531+ if headers .ContentLength >= 0 {
532+ req .ContentLength = headers .ContentLength
533+ }
518534 if headers .Host != "" {
519535 req .Host = headers .Host
520536 }
@@ -536,16 +552,9 @@ func proxyRequest(ctx context.Context, httpClient *http.Client, manager *streamM
536552 if err := manager .write (ctx , responseHeaders ); err != nil {
537553 return err
538554 }
539-
540- respBody , err := io .ReadAll (resp .Body )
541- if err != nil {
555+ if err := sendBodyReader (ctx , manager , env , protocol .TypeHTTPRespBody , resp .Body , maxTunnelBodyBytes ()); err != nil {
542556 return err
543557 }
544- if len (respBody ) > 0 {
545- if err := manager .write (ctx , protocol.Envelope {Version : 1 , Type : protocol .TypeHTTPRespBody , SessionID : env .SessionID , StreamID : env .StreamID , RouteID : env .RouteID , Timestamp : time .Now ().UTC (), Payload : protocol.HTTPBodyPayload {Base64 : true , Data : base64 .StdEncoding .EncodeToString (respBody )}}); err != nil {
546- return err
547- }
548- }
549558 return manager .write (ctx , protocol.Envelope {Version : 1 , Type : protocol .TypeHTTPRespEnd , SessionID : env .SessionID , StreamID : env .StreamID , RouteID : env .RouteID , Timestamp : time .Now ().UTC (), Payload : map [string ]any {}})
550559}
551560
@@ -599,6 +608,66 @@ func decodeBody(payload protocol.HTTPBodyPayload) ([]byte, error) {
599608 return base64 .StdEncoding .DecodeString (payload .Data )
600609}
601610
611+ func maxTunnelBodyBytes () int64 {
612+ const fallback = int64 (10 << 20 )
613+ value := strings .TrimSpace (os .Getenv ("CINDER_MAX_BODY_BYTES" ))
614+ if value == "" {
615+ return fallback
616+ }
617+ parsed , err := strconv .ParseInt (value , 10 , 64 )
618+ if err != nil || parsed <= 0 {
619+ return fallback
620+ }
621+ return parsed
622+ }
623+
624+ func sendBodyReader (ctx context.Context , manager * streamManager , env protocol.IncomingEnvelope , frameType string , body io.Reader , maxBytes int64 ) error {
625+ if body == nil {
626+ return nil
627+ }
628+ remaining := maxBytes
629+ buf := make ([]byte , httpBodyChunkSize )
630+ for {
631+ readSize := len (buf )
632+ if maxBytes > 0 && int64 (readSize ) > remaining + 1 {
633+ readSize = int (remaining + 1 )
634+ }
635+ n , err := body .Read (buf [:readSize ])
636+ if n > 0 {
637+ if maxBytes > 0 && int64 (n ) > remaining {
638+ return fmt .Errorf ("body exceeds max size of %d bytes" , maxBytes )
639+ }
640+ if maxBytes > 0 {
641+ remaining -= int64 (n )
642+ }
643+ if err := manager .write (ctx , protocol.Envelope {Version : 1 , Type : frameType , SessionID : env .SessionID , StreamID : env .StreamID , RouteID : env .RouteID , Timestamp : time .Now ().UTC (), Payload : protocol.HTTPBodyPayload {Base64 : true , Data : base64 .StdEncoding .EncodeToString (buf [:n ])}}); err != nil {
644+ return err
645+ }
646+ }
647+ if err != nil {
648+ if err == io .EOF {
649+ return nil
650+ }
651+ return err
652+ }
653+ }
654+ }
655+
656+ func readBodyWithLimit (r io.Reader , maxBytes int64 ) ([]byte , error ) {
657+ if maxBytes <= 0 {
658+ return io .ReadAll (r )
659+ }
660+ limited := io .LimitReader (r , maxBytes + 1 )
661+ body , err := io .ReadAll (limited )
662+ if err != nil {
663+ return nil , err
664+ }
665+ if int64 (len (body )) > maxBytes {
666+ return nil , fmt .Errorf ("body exceeds max size of %d bytes" , maxBytes )
667+ }
668+ return body , nil
669+ }
670+
602671func stripHopByHopHeaders (headers map [string ][]string ) map [string ][]string {
603672 if len (headers ) == 0 {
604673 return nil
0 commit comments