Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ test-%-tinygo: TINYGO_TARGET := ./targets/fastly-compute-wasip1.json
test-e2e-tinygo: TINYGO_TARGET := ./targets/fastly-compute-wasip1-serve.json
build-examples-tinygo: TINYGO_TARGET ?= ./targets/fastly-compute-wasip1.json

# Run e2e tests sequentially to avoid port conflicts:
test-e2e-%: GO_TEST_FLAGS += -p 1

# Allow `test -exec` and tinygo's emulator target to find `serve.sh`:
test-e2e-%: export PATH := $(PWD)/end_to_end_tests:$(PATH)

Expand Down
File renamed without changes.
15 changes: 15 additions & 0 deletions end_to_end_tests/serve-many/fastly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file describes a Fastly Compute package. To learn more visit:
# https://www.fastly.com/documentation/reference/compute/fastly-toml

authors = []
description = ""
language = "go"
manifest_version = 3
name = "serve-many test"
service_id = ""

[scripts]
build = "GOARCH=wasm GOOS=wasip1 go build -tags fastlyinternaldebug -o bin/main.wasm ."

[local_server.backends.self]
url = "http://127.0.0.1:23456/"
34 changes: 34 additions & 0 deletions end_to_end_tests/serve-many/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build !test

package main

import (
"context"
"fmt"
"os"
"time"

"github.com/fastly/compute-sdk-go/fsthttp"
)

func main() {
handler := func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
if r.Header.Get("Close-Session") == "1" {
opts := fsthttp.ServeManyOptionsFromContext(ctx)
opts.MaxRequests = 1
}

sessionID, requestID := os.Getenv("FASTLY_TRACE_ID"), r.RequestID
fmt.Printf("Session ID: %s, Request ID: %s\n", sessionID, requestID)

w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Session-ID", sessionID)
w.Header().Set("Request-ID", requestID)
w.Write([]byte("OK"))
}
fsthttp.ServeMany(handler, &fsthttp.ServeManyOptions{
NextTimeout: 5 * time.Second,
MaxRequests: 100,
MaxLifetime: 10 * time.Second,
})
}
80 changes: 80 additions & 0 deletions end_to_end_tests/serve-many/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//go:build ((tinygo.wasm && wasi) || wasip1) && !nofastlyhostcalls

package main

import (
"context"
"testing"

"github.com/fastly/compute-sdk-go/fsthttp"
)

func TestSessionReuse(t *testing.T) {
// First request. Session ID and request ID should match.
req, err := fsthttp.NewRequest("GET", "http://anyplace.horse", nil)
if err != nil {
t.Fatal(err)
}
resp, err := req.Send(context.Background(), "self")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

sessionID, requestID := resp.Header.Get("Session-ID"), resp.Header.Get("Request-ID")

if sessionID == "" || requestID == "" {
t.Fatalf("Session-ID and/or Request-ID are empty: %s, %s", sessionID, requestID)
}
if sessionID != requestID {
t.Errorf("sessionID = %s, requestID = %s; expected them to match", sessionID, requestID)
}
prevSessionID := sessionID

// Second request. This should reuse the session, so the session ID
// should match the previous session ID and the request ID should
// not match.
//
// We also set a header to tell the server to not allow any more
// requests on this session.
req, err = fsthttp.NewRequest("GET", "http://anyplace.horse", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Close-Session", "1")
resp, err = req.Send(context.Background(), "self")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

sessionID, requestID = resp.Header.Get("Session-ID"), resp.Header.Get("Request-ID")

if sessionID != prevSessionID {
t.Errorf("sessionID = %s, previous sessionID = %s; expected them to match", sessionID, sessionID)
}
if sessionID == requestID {
t.Errorf("sessionID = %s, requestID = %s; expected them to differ", sessionID, requestID)
}
prevSessionID = sessionID

// Third request, we should have a new session ID and it should match the request ID
req, err = fsthttp.NewRequest("GET", "http://anyplace.horse", nil)
if err != nil {
t.Fatal(err)
}
resp, err = req.Send(context.Background(), "self")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()

sessionID, requestID = resp.Header.Get("Session-ID"), resp.Header.Get("Request-ID")

if sessionID == prevSessionID {
t.Errorf("sessionID = %s, previous sessionID = %s; expected them to differ", sessionID, sessionID)
}
if sessionID != requestID {
t.Errorf("sessionID = %s, requestID = %s; expected them to match", sessionID, requestID)
}
}
25 changes: 20 additions & 5 deletions fsthttp/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ func Serve(h Handler) {
panic(fmt.Errorf("get client handles: %w", err))
}

serve(h, abireq, abibody)
serve(context.Background(), h, abireq, abibody)

// wait for any stale-while-revalidate goroutines to complete.
guestCacheSWRPending.Wait()
}

func serve(h Handler, abireq *fastly.HTTPRequest, abibody *fastly.HTTPBody) {
ctx, cancel := context.WithCancel(context.Background())
func serve(ctx context.Context, h Handler, abireq *fastly.HTTPRequest, abibody *fastly.HTTPBody) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

clientRequest, err := newClientRequest(abireq, abibody)
Expand Down Expand Up @@ -60,11 +60,13 @@ type ServeManyOptions struct {
func ServeMany(h HandlerFunc, serveOpts *ServeManyOptions) {
start := time.Now()

ctx := context.WithValue(context.Background(), serveManyOptionsKey{}, serveOpts)

abireq, abibody, err := fastly.BodyDownstreamGet()
if err != nil {
panic(fmt.Errorf("get client handles: %w", err))
}
serve(h, abireq, abibody)
serve(ctx, h, abireq, abibody)

// Serve the rest
var requests int
Expand Down Expand Up @@ -96,7 +98,7 @@ func ServeMany(h HandlerFunc, serveOpts *ServeManyOptions) {
panic(fmt.Errorf("get client handles: %w", err))
}

serve(h, abireq, abibody)
serve(ctx, h, abireq, abibody)
}

// wait for any stale-while-revalidate goroutines to complete.
Expand All @@ -122,3 +124,16 @@ type HandlerFunc func(ctx context.Context, w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(ctx context.Context, w ResponseWriter, r *Request) {
f(ctx, w, r)
}

type serveManyOptionsKey struct{}

// ServeManyOptionsFromContext retrieves the [ServeManyOptions] value
// passed into [ServeMany]. It will return nil if [Serve] or
// [ServeFunc] were used.
func ServeManyOptionsFromContext(ctx context.Context) *ServeManyOptions {
opts, ok := ctx.Value(serveManyOptionsKey{}).(*ServeManyOptions)
if !ok {
return nil
}
return opts
}
5 changes: 5 additions & 0 deletions integration_tests/request_downstream/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func TestDownstreamRequest(t *testing.T) {
// Viceroy constructs a simple GET http://example.com request with
// the remote address being 127.0.0.1, so that's what we check for
// here.
handlerRun := false
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
handlerRun = true
if r.Method != "GET" {
t.Errorf("Method = %s, want GET", r.Method)
return
Expand All @@ -44,6 +46,9 @@ func TestDownstreamRequest(t *testing.T) {
return
}
})
if !handlerRun {
t.Errorf("handler was not run")
}
}

func TestDownstreamResponse(t *testing.T) {
Expand Down