Skip to content

Commit 1fc3414

Browse files
Merge pull request #19 from speakeasy-api/improve-url-parsing
fix: handle url resolution correctly
2 parents 6dd8a29 + 8763ebf commit 1fc3414

File tree

2 files changed

+135
-10
lines changed

2 files changed

+135
-10
lines changed

harbuilder.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ func (h *harBuilder) buildHarFile(ctx context.Context, cw *captureWriter, r *htt
5151
}
5252

5353
func getResolvedURL(r *http.Request, c *controller) *url.URL {
54-
var url *url.URL
54+
req := *r
5555

5656
// Taking advantage of Gorilla's ProxyHeaders parsing to resolve Forwarded headers
5757
handlers.ProxyHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58-
url = r.URL
59-
})).ServeHTTP(nil, r)
58+
req = *r
59+
})).ServeHTTP(nil, &req)
60+
61+
url := req.URL
6062

6163
queryParams := url.Query()
6264
for key, values := range queryParams {
@@ -73,20 +75,20 @@ func getResolvedURL(r *http.Request, c *controller) *url.URL {
7375
}
7476
url.RawQuery = queryParams.Encode()
7577

76-
if url.IsAbs() {
77-
return url
78-
}
79-
8078
if url.Scheme == "" {
81-
if r.TLS != nil {
79+
if req.TLS != nil {
8280
url.Scheme = "https"
8381
} else {
8482
url.Scheme = "http"
8583
}
8684
}
8785

88-
if url.Host == "" {
89-
url.Host = r.Host
86+
if url.Host == "" || url.Host != req.Host {
87+
url.Host = req.Host
88+
}
89+
90+
if url.IsAbs() {
91+
return url
9092
}
9193

9294
return url

middleware_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package speakeasy_test
33
import (
44
"bytes"
55
"context"
6+
"crypto/tls"
67
"encoding/json"
78
"io/ioutil"
89
"log"
@@ -16,6 +17,7 @@ import (
1617
"testing"
1718
"time"
1819

20+
"github.com/chromedp/cdproto/har"
1921
"github.com/gin-gonic/gin"
2022
"github.com/go-chi/chi/v5"
2123
"github.com/gorilla/mux"
@@ -269,6 +271,127 @@ func TestSpeakeasy_Middleware_Capture_Success(t *testing.T) {
269271
}
270272
}
271273

274+
func TestSpeakeasy_Middleware_URL_Resolve_Success(t *testing.T) {
275+
type args struct {
276+
url string
277+
headers map[string]string
278+
host string
279+
https bool
280+
}
281+
tests := []struct {
282+
name string
283+
args args
284+
wantResolvedURL string
285+
}{
286+
{
287+
name: "successfully resolves relative URL",
288+
args: args{
289+
url: "/v1/users",
290+
host: "localhost:8080",
291+
},
292+
wantResolvedURL: "http://localhost:8080/v1/users",
293+
},
294+
{
295+
name: "successfully resolves relative HTTPS URL",
296+
args: args{
297+
url: "/v1/users",
298+
host: "localhost:8080",
299+
https: true,
300+
},
301+
wantResolvedURL: "https://localhost:8080/v1/users",
302+
},
303+
{
304+
name: "successfully resolves absolute URL",
305+
args: args{
306+
url: "https://localhost:8080/v1/users",
307+
},
308+
wantResolvedURL: "https://localhost:8080/v1/users",
309+
},
310+
{
311+
name: "successfully resolves relative URL behind proxy",
312+
args: args{
313+
url: "/v1/users",
314+
host: "localhost:8080",
315+
headers: map[string]string{
316+
"X-Forwarded-Host": "dev.speakeasyapi.dev",
317+
"X-Forwarded-Proto": "https",
318+
},
319+
},
320+
wantResolvedURL: "https://dev.speakeasyapi.dev/v1/users",
321+
},
322+
{
323+
name: "successfully resolves absolute URL behind proxy",
324+
args: args{
325+
url: "http://10.0.0.1:8080/v1/users",
326+
headers: map[string]string{
327+
"X-Forwarded-Host": "dev.speakeasyapi.dev",
328+
"X-Forwarded-Proto": "https",
329+
},
330+
},
331+
wantResolvedURL: "https://dev.speakeasyapi.dev/v1/users",
332+
},
333+
}
334+
for _, tt := range tests {
335+
t.Run(tt.name, func(t *testing.T) {
336+
captured := false
337+
handled := false
338+
339+
speakeasy.ExportSetTimeNow(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC))
340+
speakeasy.ExportSetTimeSince(1 * time.Millisecond)
341+
342+
wg := &sync.WaitGroup{}
343+
wg.Add(1)
344+
345+
sdkInstance := speakeasy.New(speakeasy.Config{
346+
APIKey: testAPIKey,
347+
ApiID: testApiID,
348+
VersionID: testVersionID,
349+
GRPCDialer: dialer(func(ctx context.Context, req *ingest.IngestRequest) {
350+
var h har.HAR
351+
352+
err := json.Unmarshal([]byte(req.GetHar()), &h)
353+
require.NoError(t, err)
354+
355+
assert.Equal(t, tt.wantResolvedURL, h.Log.Entries[0].Request.URL)
356+
captured = true
357+
wg.Done()
358+
}),
359+
})
360+
361+
r := mux.NewRouter()
362+
r.Use(sdkInstance.Middleware)
363+
364+
r.Methods(http.MethodGet).Path("/v1/users").HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
365+
w.WriteHeader(http.StatusOK)
366+
handled = true
367+
})
368+
369+
w := httptest.NewRecorder()
370+
371+
req, err := http.NewRequest(http.MethodGet, tt.args.url, nil)
372+
assert.NoError(t, err)
373+
374+
if tt.args.host != "" {
375+
req.Host = tt.args.host
376+
}
377+
for k, v := range tt.args.headers {
378+
req.Header.Add(k, v)
379+
}
380+
if tt.args.https {
381+
req.TLS = &tls.ConnectionState{}
382+
}
383+
384+
r.ServeHTTP(w, req)
385+
386+
wg.Wait()
387+
388+
assert.True(t, handled, "middleware did not call handler")
389+
assert.True(t, captured, "middleware did not capture request")
390+
assert.Equal(t, http.StatusOK, w.Code)
391+
})
392+
}
393+
}
394+
272395
func TestSpeakeasy_Middleware_GorillaMux_PathHint_Success(t *testing.T) {
273396
type args struct {
274397
path string

0 commit comments

Comments
 (0)