Skip to content

Commit df16041

Browse files
committed
Make sessions work
User simonahac reported a thorny problem on discord yesterday. See discussion beginning at https://discord.com/channels/928484660785336380/928485908842426389/1210584191142723615 They want to use a service that relies on sessions. First you POST to a login URL. That sets a cookie and redirects to a resource. The resource only works if you provide the cookie value in the GET request. This works out of the box in python, Postman, and most other clients. But not pixlet. Looks like the underlying cause is the default behaviour of golang's http.Client. By default, it will ignore any cookies set in responses when making the redirect. When sending the redirected request, it would just copy over headers and cookies from the original outgoing response -- which won't have the cookie set. If the client has a CookieJar, however, it just works as expected. Cookies from responses will be added to the jar, and all the cookies from the jar will automatically be added to all outgoing requests from that point. https://cs.opensource.google/go/go/+/master:src/net/http/client.go;drc=b899e0b8cc1afc4534758c9ebe1e051e5220bfbd;l=88 I think this should be a safe change. There's only one existing community app (avatarsinpixels) that manually accesses the "Set-Cookie" header on a response. I've checked it works the same with and without this change.
1 parent 9083434 commit df16041

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed

runtime/httpcache.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import (
99
"fmt"
1010
"math/rand"
1111
"net/http"
12+
"net/http/cookiejar"
1213
"net/http/httputil"
1314
"strconv"
1415
"strings"
1516
"time"
1617

18+
"golang.org/x/net/publicsuffix"
1719
"tidbyt.dev/pixlet/runtime/modules/starlarkhttp"
1820
)
1921

@@ -54,7 +56,16 @@ func InitHTTP(cache Cache) {
5456
transport: http.DefaultTransport,
5557
}
5658

59+
// Providing a cookie jar allows sessions and redirects to work properly. With a
60+
// jar present, any cookies set in a response will automatically be added to
61+
// subsequent requests. This means that we can follow redirects after logging into
62+
// a session. Without a jar, any cookies will be dropped from redirects unless explicitly
63+
// set in the original outgoing request.
64+
// https://cs.opensource.google/go/go/+/master:src/net/http/client.go;drc=4c394b5638cc2694b1eff6418bc3e7db8132de0e;l=88
65+
jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) // never returns non-nil err
66+
5767
httpClient := &http.Client{
68+
Jar: jar,
5869
Transport: cc,
5970
Timeout: HTTPTimeout * 2,
6071
}

runtime/httpcache_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import (
55
"fmt"
66
"math/rand"
77
"net/http"
8+
"net/http/httptest"
89
"os"
10+
"strings"
911
"testing"
1012
"time"
1113

1214
"github.com/stretchr/testify/assert"
15+
"go.starlark.net/starlark"
1316
)
1417

1518
func TestInitHTTP(t *testing.T) {
@@ -183,3 +186,43 @@ func TestDetermineTTLNoHeaders(t *testing.T) {
183186
ttl := DetermineTTL(req, res)
184187
assert.Equal(t, MinRequestTTL, ttl)
185188
}
189+
190+
func TestSetCookieOnRedirect(t *testing.T) {
191+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
192+
// Requests to "/login" set a cookie and redirect to /destination
193+
if strings.HasSuffix(r.URL.Path, "/login") {
194+
w.Header().Set("Set-Cookie", "doodad=foobar; path=/; HttpOnly")
195+
w.Header().Set("Location", "/destination")
196+
w.WriteHeader(302)
197+
return
198+
}
199+
// Requests to /destination must have cookie set
200+
if strings.HasSuffix(r.URL.Path, "/destination") {
201+
c, err := r.Cookie("doodad")
202+
if err != nil {
203+
t.Errorf("Expected cookie `doodad` not present") // Occurs if client has no cookie jar
204+
}
205+
if c.Value != "foobar" {
206+
t.Errorf("Cookie `doodad` value mismatch. Expected foobar, got %s", c.Value)
207+
}
208+
if _, err := w.Write([]byte(`{"hello":"world"}`)); err != nil {
209+
t.Fatal(err)
210+
}
211+
return
212+
}
213+
t.Errorf("Unexpected path requested: %s", r.URL.Path)
214+
}))
215+
216+
starlark.Universe["test_server_url"] = starlark.String(ts.URL)
217+
c := NewInMemoryCache()
218+
InitHTTP(c)
219+
220+
b, err := os.ReadFile("testdata/httpredirect.star")
221+
assert.NoError(t, err)
222+
223+
app, err := NewApplet("httpredirect.star", b)
224+
assert.NoError(t, err)
225+
226+
_, err = app.Run(context.Background())
227+
assert.NoError(t, err)
228+
}

runtime/testdata/httpredirect.star

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
load("assert.star", "assert")
2+
load("http.star", "http")
3+
load("render.star", "render")
4+
5+
def main():
6+
res_1 = http.post(test_server_url + "/login")
7+
assert.eq(res_1.status_code, 200)
8+
assert.eq(res_1.body(), '{"hello":"world"}')
9+
assert.eq(res_1.json(), {"hello": "world"})
10+
return render.Root(child = render.Text("pass"))

0 commit comments

Comments
 (0)