multipartHTTP

package module
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2025 License: MIT Imports: 24 Imported by: 0

README

xk6-multipart-http

A k6 extension that adds support for testing HTTP multipart streaming responses, enabling load testing of http multipart/mixed GraphQL subscriptions.

What is this?

k6 natively only supports traditional HTTP request-response patterns where all data is returned at once. This extension bridges that gap by providing WebSocket-like functionality for HTTP multipart streams, specifically targeting:

  • GraphQL Subscriptions using the multipart protocol
  • Apollo Router Subscriptions
  • Server-Sent Events over multipart HTTP
  • Any streaming HTTP endpoint using multipart/mixed content type

How it works

The extension implements an event-driven architecture that:

  1. Establishes HTTP connections with multipart/mixed content type
  2. Parses streaming multipart responses in real-time using boundary delimiters (--graphql)
  3. Emits JavaScript events (open, message, close, error) similar to WebSocket API
  4. Integrates with k6 metrics to track session duration, message counts, and connection states
  5. Handles multiple payload formats including GraphQL {"payload":...} and {"data":...} structures

Project structure

├── module.go           # k6 module registration and per-VU instance creation
├── multipartHTTP.go    # Core subscription client with event handling
├── client.go           # HTTP connection and multipart response parsing
├── listeners.go        # JavaScript event system implementation
├── events/events.go    # Event type definitions and utilities
├── metrics.go          # Custom k6 metrics integration
├── params.go           # Parameter parsing for connection options
└── timer_example/      # Complete GraphQL server example for testing

Usage

JavaScript API
import multipartHTTP from 'k6/x/multipartHTTP';

export default function() {
  // GraphQL subscription example
  const subscription = multipartHTTP.MultipartHttp('http://localhost:8080/query', {
    method: 'POST',
    headers: {
      'Accept': 'multipart/mixed; boundary="graphql"; subscriptionSpec=1.0',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: 'subscription { time { time } }'
    })
  });

  // Event handlers (similar to WebSocket API)
  subscription.addEventListener('open', () => {
    console.log('Connection opened');
  });

  subscription.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
    console.log('Received:', data);
  });

  subscription.addEventListener('close', (event) => {
    console.log('Connection closed');
  });

  subscription.addEventListener('error', (event) => {
    console.log('Error occurred:', event);
  });

  // Close connection after 10 seconds
  setTimeout(() => {
    subscription.close();
  }, 10000);
}

Requirements

  • xk6 (go install go.k6.io/xk6/cmd/xk6@latest)

Getting started

  1. Build the k6 binary with the extension:

    # Using Docker (recommended)
    make compile-docker
    
    # Or build locally
    make k6
    
  2. Run the example:

    # Start the test GraphQL server
    cd timer_example && go run server.go
    
    # In another terminal, run the k6 test
    ./k6 run timer_example/timer_example.js
    

Protocol support

  • Content-Type: multipart/mixed; boundary="graphql"; subscriptionSpec=1.0
  • Boundary parsing: Handles --graphql multipart boundaries
  • GraphQL formats: Supports both Apollo Server ({"data":...}) and standard ({"payload":...}) formats
  • Heartbeat detection: Recognizes {} heartbeat messages
  • Connection states: Implements WebSocket-like ReadyState pattern (CONNECTING, OPEN, CLOSING, CLOSED)

Documentation

Index

Constants

View Source
const (
	HTTPMultipartSessionsName         = "http_multipart_sessions"
	HTTPMultipartMessagesReceivedName = "http_multipart_msgs_received"
	HTTPMultipartSessionDurationName  = "http_multipart_session_duration"
)

Variables

View Source
var (
	ErrNoBoundary    = errors.New("no boundary found")
	ErrInvalidFormat = errors.New("invalid multipart format")
	ErrUnexpectedEOF = errors.New("unexpected end of file")
)

Functions

This section is empty.

Types

type Client

type Client struct {
	Logger logrus.FieldLogger
	// contains filtered or unexported fields
}

Client is the representation of the sse returned to the js.

func (*Client) Close

func (c *Client) Close() error

Close the event loop

type HTTPMultipartMetrics

type HTTPMultipartMetrics struct {
	// Websocket-related
	HTTPMultipartSessions         *metrics.Metric
	HTTPMultipartMessagesReceived *metrics.Metric
	HTTPMultipartSessionDuration  *metrics.Metric
}

BuiltinMetrics represent all the builtin metrics of k6

func RegisterMetrics

func RegisterMetrics(registry *metrics.Registry) *HTTPMultipartMetrics

RegisterMetrics register and returns the builtin metrics in the provided registry

type InternalState

type InternalState struct {
	ActiveVUs       int64 `js:"activeVUs"`
	Iteration       int64
	VUID            uint64      `js:"vuID"`
	VUIDFromRuntime sobek.Value `js:"vuIDFromRuntime"`
}

InternalState holds basic metadata from the runtime state.

type MultipartSubscription

type MultipartSubscription struct {
	// contains filtered or unexported fields
}

MultipartSubscription represents an instance of the JS module for every VU.

type MultipartSubscriptionAPI

type MultipartSubscriptionAPI struct {
	// contains filtered or unexported fields
}

func (*MultipartSubscriptionAPI) Exports

Exports implements the modules.Instance interface and returns the exported types for the JS module.

func (*MultipartSubscriptionAPI) GetInternalState

func (r *MultipartSubscriptionAPI) GetInternalState() *InternalState

GetInternalState interrogates the current virtual user for state information.

type ObjectHolder added in v0.0.2

type ObjectHolder struct {
	// contains filtered or unexported fields
}

Hold MultipartSubscription JS objects so they don't get GC'd until Closed

type Parser added in v0.0.2

type Parser struct {
	// contains filtered or unexported fields
}

Parser handles parsing of multipart/mixed content according to RFC 1341

func NewParser added in v0.0.2

func NewParser(r io.Reader, contentType string) (*Parser, error)

NewParser creates a new multipart parser contentType should be the full Content-Type header value (e.g., "multipart/mixed; boundary=example")

func (*Parser) NextPart added in v0.0.2

func (p *Parser) NextPart() (*Part, error)

NextPart reads and returns the next part from the multipart stream Returns io.EOF when no more parts are available

type Part added in v0.0.2

type Part struct {
	Header textproto.MIMEHeader
	Body   []byte
}

Part represents a single part in a multipart message

func (*Part) GetContentDisposition added in v0.0.2

func (p *Part) GetContentDisposition() string

GetContentDisposition returns the Content-Disposition header value for a part

func (*Part) GetContentType added in v0.0.2

func (p *Part) GetContentType() string

GetContentType returns the Content-Type header value for a part

func (*Part) IsBinary added in v0.0.2

func (p *Part) IsBinary() bool

IsBinary returns true if the part contains binary data

func (*Part) IsText added in v0.0.2

func (p *Part) IsText() bool

IsText returns true if the part contains text data

func (*Part) String added in v0.0.2

func (p *Part) String() string

String returns the body as a string (useful for text parts)

type Payload

type Payload struct {
	// contains filtered or unexported fields
}

type ReadyState

type ReadyState uint8
const (
	// CONNECTING is the state while the web socket is connecting
	CONNECTING ReadyState = iota
	// OPEN is the state after the connection is established and before it starts closing
	OPEN
	// CLOSING is while the connection is closing but is *not* closed yet
	CLOSING
	// CLOSED is when the connection is finally closed
	CLOSED
)

type RootModule

type RootModule struct{}

RootModule is the global module instance that will create module instances for each VU.

func New

func New() *RootModule

New returns a pointer to a new RootModule instance.

func (*RootModule) NewModuleInstance

func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance

NewMultipartSubscription implements the modules.Module interface returning a new instance for each VU.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL