Skip to content

Commit d5e15df

Browse files
committed
add support for X-Up-Timeout header field. Closes #815
the use-case was to bypass API Gateway by calling the Lambda function directly with a request event, thus no longer bound to the proxy.timeout max of 25s
1 parent c46d5b6 commit d5e15df

File tree

2 files changed

+47
-12
lines changed

2 files changed

+47
-12
lines changed

http/relay/relay.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/url"
1212
"os"
1313
"os/exec"
14+
"strconv"
1415
"sync"
1516
"time"
1617

@@ -74,21 +75,11 @@ func New(c *up.Config) (http.Handler, error) {
7475

7576
timeout := time.Duration(c.Proxy.Timeout) * time.Second
7677

77-
transport := &http.Transport{
78-
DialContext: (&net.Dialer{
79-
Timeout: 2 * time.Second,
80-
KeepAlive: 2 * time.Second,
81-
DualStack: true,
82-
}).DialContext,
83-
ResponseHeaderTimeout: timeout,
84-
DisableKeepAlives: true,
85-
}
86-
8778
p := &Proxy{
8879
config: c,
8980
stdout: writer.New(stdout, ctx),
9081
stderr: writer.New(stderr, ctx),
91-
transport: transport,
82+
transport: newTransport(timeout),
9283
}
9384

9485
if err := p.Start(); err != nil {
@@ -145,8 +136,16 @@ func (p *Proxy) Restart() error {
145136
func (p *Proxy) RoundTrip(r *http.Request) (*http.Response, error) {
146137
id := r.Header.Get("X-Request-Id")
147138
ctx = ctx.WithField("id", id)
139+
transport := p.transport
140+
141+
// timeout header
142+
if s := r.Header.Get("X-Up-Timeout"); s != "" {
143+
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
144+
transport = newTransport(time.Duration(n) * time.Second)
145+
}
146+
}
148147

149-
res, err := p.transport.RoundTrip(r)
148+
res, err := transport.RoundTrip(r)
150149

151150
// timeout error
152151
if e, ok := err.(net.Error); ok && e.Timeout() {
@@ -213,6 +212,19 @@ func (p *Proxy) command(s string, env []string) *exec.Cmd {
213212
return cmd
214213
}
215214

215+
// newTransport returns a new http.Transport with the given timeout.
216+
func newTransport(timeout time.Duration) *http.Transport {
217+
return &http.Transport{
218+
DialContext: (&net.Dialer{
219+
Timeout: 2 * time.Second,
220+
KeepAlive: 2 * time.Second,
221+
DualStack: true,
222+
}).DialContext,
223+
ResponseHeaderTimeout: timeout,
224+
DisableKeepAlives: true,
225+
}
226+
}
227+
216228
// env returns an environment variable.
217229
func env(name string, val interface{}) string {
218230
return fmt.Sprintf("%s=%v", name, val)

http/relay/relay_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ func TestRelay(t *testing.T) {
145145
assert.Equal(t, 200, res.Code)
146146
assertString(t, "Hello World", res.Body.String())
147147
})
148+
149+
t.Run("timeout header field", func(t *testing.T) {
150+
newHandler(t)
151+
152+
// first
153+
start := time.Now()
154+
res := httptest.NewRecorder()
155+
req := httptest.NewRequest("GET", "/timeout", nil)
156+
req.Header.Set("X-Up-Timeout", "1")
157+
h.ServeHTTP(res, req)
158+
159+
assert.True(t, time.Since(start) < time.Second*2)
160+
assert.Equal(t, 502, res.Code)
161+
assertString(t, "", res.Body.String())
162+
163+
// second
164+
res = httptest.NewRecorder()
165+
req = httptest.NewRequest("GET", "/hello", nil)
166+
h.ServeHTTP(res, req)
167+
168+
assert.Equal(t, 200, res.Code)
169+
assertString(t, "Hello World", res.Body.String())
170+
})
148171
}
149172

150173
func assertString(t testing.TB, want, got string) {

0 commit comments

Comments
 (0)