7
7
"context"
8
8
"errors"
9
9
"fmt"
10
+ "io"
10
11
"log/slog"
11
12
"net/http"
12
13
"os"
@@ -20,49 +21,177 @@ import (
20
21
wrpcnats "github.com/wrpc/wrpc/go/nats"
21
22
)
22
23
24
+ type incomingBody struct {
25
+ body io.Reader
26
+ trailer http.Header
27
+ trailerRx wrpc.Receiver [[]* wrpc.Tuple2 [string , [][]byte ]]
28
+ }
29
+
30
+ func (r * incomingBody ) Read (b []byte ) (int , error ) {
31
+ n , err := r .body .Read (b )
32
+ if err == io .EOF {
33
+ trailers , err := r .trailerRx .Receive ()
34
+ if err != nil {
35
+ return 0 , fmt .Errorf ("failed to receive trailers: %w" , err )
36
+ }
37
+ for _ , header := range trailers {
38
+ for _ , value := range header .V1 {
39
+ r .trailer .Add (header .V0 , string (value ))
40
+ }
41
+ }
42
+ return n , io .EOF
43
+ }
44
+ return n , err
45
+ }
46
+
47
+ type outgoingBody struct {
48
+ body io.ReadCloser
49
+ trailer http.Header
50
+ trailerCh chan <- []* wrpc.Tuple2 [string , [][]byte ]
51
+ }
52
+
53
+ func (r * outgoingBody ) Read (b []byte ) (int , error ) {
54
+ slog .Debug ("reading HTTP body chunk" , "len" , len (b ))
55
+ n , err := r .body .Read (b )
56
+ slog .Debug ("read HTTP body chunk" , "len" , n )
57
+ if err == io .EOF {
58
+ slog .Debug ("HTTP body reached EOF, reading trailers" )
59
+ trailers := make ([]* wrpc.Tuple2 [string , [][]byte ], 0 , len (r .trailer ))
60
+ for header , values := range r .trailer {
61
+ bs := make ([][]byte , len (values ))
62
+ for i , value := range values {
63
+ bs [i ] = []byte (value )
64
+ }
65
+ trailers = append (trailers , & wrpc.Tuple2 [string , [][]byte ]{V0 : header , V1 : bs })
66
+ }
67
+ slog .Debug ("sending trailers" )
68
+ r .trailerCh <- trailers
69
+ close (r .trailerCh )
70
+ return n , io .EOF
71
+ }
72
+ return n , err
73
+ }
74
+
75
+ func (r * outgoingBody ) Close () error {
76
+ return r .body .Close ()
77
+ }
78
+
79
+ type trailerReceiver <- chan []* wrpc.Tuple2 [string , [][]byte ]
80
+
81
+ func (r trailerReceiver ) Receive () ([]* wrpc.Tuple2 [string , [][]byte ], error ) {
82
+ trailers , ok := <- r
83
+ if ! ok {
84
+ return nil , errors .New ("trailer receiver channel closed" )
85
+ }
86
+ return trailers , nil
87
+ }
88
+
89
+ func (r trailerReceiver ) Ready () bool {
90
+ return false
91
+ }
92
+
23
93
func run () error {
24
94
nc , err := nats .Connect (nats .DefaultURL )
25
95
if err != nil {
26
96
return fmt .Errorf ("failed to connect to NATS.io: %w" , err )
27
97
}
28
98
defer nc .Close ()
29
99
30
- stop , err := outgoing_handler .ServeHandle (wrpcnats .NewClient (nc , "go" ), func (ctx context.Context , request * types.RecordRequest , opts * types.RecordRequestOptions ) (* types.RecordResponse , error ) {
31
- scheme := request .Scheme .String ()
32
-
100
+ stop , err := outgoing_handler .ServeHandle (wrpcnats .NewClient (nc , "go" ), func (ctx context.Context , request * types.RequestRecord , opts * types.RequestOptionsRecord ) (* wrpc.Result [types.ResponseRecord , types.ErrorCodeVariant ], func (), error ) {
101
+ var scheme string
102
+ switch disc := request .Scheme .Discriminant (); disc {
103
+ case types .SchemeDiscriminant_Http :
104
+ scheme = "http"
105
+ case types .SchemeDiscriminant_Https :
106
+ scheme = "https"
107
+ case types .SchemeDiscriminant_Other :
108
+ var ok bool
109
+ scheme , ok = request .Scheme .GetOther ()
110
+ if ! ok {
111
+ return nil , nil , errors .New ("invalid scheme" )
112
+ }
113
+ default :
114
+ return nil , nil , fmt .Errorf ("invalid scheme discriminant %v" , disc )
115
+ }
33
116
authority := ""
34
117
if request .Authority != nil {
35
118
authority = * request .Authority
36
119
}
37
-
38
120
pathWithQuery := ""
39
121
if request .PathWithQuery != nil {
40
122
pathWithQuery = * request .PathWithQuery
41
123
}
124
+ url := fmt .Sprintf ("%s://%s/%s" , scheme , authority , pathWithQuery )
42
125
43
- switch request .Method .Discriminant () {
44
- case types .DiscriminantMethod_Get :
45
- resp , err := http .Get (fmt .Sprintf ("%s://%s/%s" , scheme , authority , pathWithQuery ))
46
- if err != nil {
47
- return nil , fmt .Errorf ("request failed: %w" , err )
48
- }
49
- headers := make ([]* wrpc.Tuple2 [string , [][]byte ], 0 , len (resp .Header ))
50
- for header , values := range resp .Header {
51
- bs := make ([][]byte , len (values ))
52
- for i , value := range values {
53
- bs [i ] = []byte (value )
54
- }
55
- headers = append (headers , & wrpc.Tuple2 [string , [][]byte ]{V0 : header , V1 : bs })
126
+ var method string
127
+ switch disc := request .Method .Discriminant (); disc {
128
+ case types .MethodDiscriminant_Get :
129
+ method = "get"
130
+ case types .MethodDiscriminant_Head :
131
+ method = "head"
132
+ case types .MethodDiscriminant_Post :
133
+ method = "post"
134
+ case types .MethodDiscriminant_Put :
135
+ method = "put"
136
+ case types .MethodDiscriminant_Delete :
137
+ method = "delete"
138
+ case types .MethodDiscriminant_Connect :
139
+ method = "connect"
140
+ case types .MethodDiscriminant_Options :
141
+ method = "options"
142
+ case types .MethodDiscriminant_Trace :
143
+ method = "trace"
144
+ case types .MethodDiscriminant_Patch :
145
+ method = "patch"
146
+ case types .MethodDiscriminant_Other :
147
+ var ok bool
148
+ method , ok = request .Method .GetOther ()
149
+ if ! ok {
150
+ return nil , nil , errors .New ("invalid HTTP method" )
56
151
}
57
- return & types.RecordResponse {
58
- Body : wrpc .NewPendingByteReader (bufio .NewReader (resp .Body )),
59
- Trailers : nil , // Trailers wrpc.ReadyReceiver[[]*wrpc.Tuple2[string, [][]byte]]
60
- Status : uint16 (resp .StatusCode ),
61
- Headers : headers ,
62
- }, nil
63
152
default :
64
- return nil , errors . New ( "only GET currently supported" )
153
+ return nil , nil , fmt . Errorf ( "invalid HTTP method discriminant %v" , disc )
65
154
}
155
+
156
+ var trailer http.Header
157
+ req , err := http .NewRequest (method , url , & incomingBody {body : request .Body , trailer : trailer , trailerRx : request .Trailers })
158
+ if err != nil {
159
+ return nil , nil , fmt .Errorf ("failed to construct a new HTTP request: %w" , err )
160
+ }
161
+ req .Trailer = trailer
162
+ for _ , header := range request .Headers {
163
+ for _ , value := range header .V1 {
164
+ req .Header .Add (header .V0 , string (value ))
165
+ }
166
+ }
167
+ resp , err := http .DefaultClient .Do (req )
168
+ if err != nil {
169
+ return nil , nil , fmt .Errorf ("request failed: %w" , err )
170
+ }
171
+
172
+ headers := make ([]* wrpc.Tuple2 [string , [][]byte ], 0 , len (resp .Header ))
173
+ for header , values := range resp .Header {
174
+ bs := make ([][]byte , len (values ))
175
+ for i , value := range values {
176
+ bs [i ] = []byte (value )
177
+ }
178
+ headers = append (headers , & wrpc.Tuple2 [string , [][]byte ]{V0 : header , V1 : bs })
179
+ }
180
+
181
+ trailerCh := make (chan []* wrpc.Tuple2 [string , [][]byte ], 1 )
182
+ body := & outgoingBody {body : resp .Body , trailer : resp .Trailer , trailerCh : trailerCh }
183
+ return & wrpc.Result [types.ResponseRecord , types.ErrorCodeVariant ]{
184
+ Ok : & types.ResponseRecord {
185
+ Body : wrpc .NewPendingByteReader (bufio .NewReader (body )),
186
+ Trailers : trailerReceiver (trailerCh ),
187
+ Status : uint16 (resp .StatusCode ),
188
+ Headers : headers ,
189
+ },
190
+ }, func () {
191
+ if err := body .Close (); err != nil {
192
+ slog .Warn ("failed to close GET response body" , "err" , err )
193
+ }
194
+ }, nil
66
195
})
67
196
if err != nil {
68
197
return err
@@ -82,7 +211,7 @@ func run() error {
82
211
83
212
func init () {
84
213
slog .SetDefault (slog .New (slog .NewTextHandler (os .Stderr , & slog.HandlerOptions {
85
- AddSource : true , Level : slog .LevelDebug , ReplaceAttr : func (groups []string , a slog.Attr ) slog.Attr {
214
+ Level : slog .LevelDebug , ReplaceAttr : func (groups []string , a slog.Attr ) slog.Attr {
86
215
if a .Key == slog .TimeKey {
87
216
return slog.Attr {}
88
217
}
0 commit comments