webtransport

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2022 License: MIT Imports: 12 Imported by: 1

README

webtransport-go

This package provides a lightweight WebTransport-over-HTTP/3 server implementation in Go.

What is WebTransport?

WebTransport (https://www.w3.org/TR/webtransport/) is a 21st century replacement for WebSockets. It's currently supported by Chrome, with support in other browsers coming shortly. It has several benefits over WebSockets:

  • It's built on top of HTTP/3 which uses QUIC (not TCP) as the transport. QUIC provides significant performance advantages over TCP, especially on congested networks.

  • Unlike WebSockets where you get just a single send/receive stream, WebTransport provides for multiple server-initiated or client-initiated unidirectional or bidirectional (reliable) streams, plus (unreliable) datagrams.

  • Unlike XHR/fetch calls, WebTransport provides a means (serverCertificateHashes) for a browser to communicate securely with endpoints where issuance of Web PKI certificates is not possible, for example, a device over the LAN which is only accessible via a private IP address and hence a self-signed certificate. This is potentially big for IoT.

Neither WebTransport nor HTTP/3 are standardized yet. We adhere to: draft-ietf-quic-http-34 and draft-ietf-webtrans-http3-02.

Complete Go server and browser-based client example

Take a look at the webtransport-go-example repo for a complete server (and browser client) example.

Minimal "getting started" example

You'll need to get a certificate for your server. Please read the comments at the top of Google's WebTransport server example in Python for detailed instructions.

First, set up WebTransport server parameters:

server := &webtransport.Server{
	ListenAddr:     ":4433",
	TLSCert:        webtransport.CertFile{Path: "cert.pem"},
	TLSKey:         webtransport.CertFile{Path: "cert.key"},
	AllowedOrigins: []string{"googlechrome.github.io", "127.0.0.1:8000", "localhost:8000"},
}

Then, set up an http.Handler to accept a session, wait for an incoming bidirectional stream from the client, then (in this example) receive data and echo it back:

http.HandleFunc("/counter", func(rw http.ResponseWriter, r *http.Request) {
	session := r.Body.(*webtransport.Session)
	session.AcceptSession()
	defer session.CloseSession()

	// Wait for incoming bidi stream
	s, err := session.AcceptStream()
	if err != nil {
		return
	}

	for {
		buf := make([]byte, 1024)
		n, err := s.Read(buf)
		if err != nil {
			break
		}
		fmt.Printf("Received from bidi stream %v: %s\n", s.StreamID(), buf[:n])
		sendMsg := bytes.ToUpper(buf[:n])
		fmt.Printf("Sending to bidi stream %v: %s\n", s.StreamID(), sendMsg)
		s.Write(sendMsg)
	}
}

Finally, start the server:

ctx, cancel := context.WithCancel(context.Background())
server.Run(ctx)

Here is a simple Chrome browser client to talk to this server. You can open a new browser tab and paste it into the Chrome DevTools console:

let transport = new WebTransport("https://localhost:4433/counter");
await transport.ready;
let stream = await transport.createBidirectionalStream();
let encoder = new TextEncoder();
let decoder = new TextDecoder();
let writer = stream.writable.getWriter();
let reader = stream.readable.getReader();
await writer.write(encoder.encode("Hello, world!"))
console.log(decoder.decode((await reader.read()).value));
transport.close();

Authors and acknowledgment

This package was written by me, drawing on several sources of inspiration:

  • Experimenting with QUIC and WebTransport in Go which describes the implementation in Go of a simple server for WebTransport over QUIC, which is now defunct but formed the basis for the WebTransport over HTTP/3 draft standard.
  • Pull request #3256 for the quic-go package, which implements (an earlier draft spec for) WebTransport but is a heavyweight structural change for the package for which there doesn't appear any appetite from the package maintainer.
  • HTTP/3 server built into quic-go. This package doesn't use quic-go's HTTP/3 server (only the underlying QUIC transport implementation), but webtransport-go implements its own minimal HTTP/3 server as needed to support WebTransport.

Instead, webtransport-go implements the latest draft standards for all the supported protocols, is very lightweight, and doesn't depend on any non-standard Go package forks or WIP modules.

License

Provided under the MIT license.

Documentation

Overview

Package webtransport provides a lightweight WebTransport-over-HTTP/3 server implementation in Go.

WebTransport (https://www.w3.org/TR/webtransport/) is a 21st century replacement for WebSockets. It's currently supported by Chrome, with support in other browsers coming shortly.

Neither WebTransport nor HTTP/3 are standardized yet. We adhere to: draft-ietf-quic-http-34 https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34 and draft-ietf-webtrans-http3-02 https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3-02

You can find a complete server (and browser client) example here: https://github.com/adriancable/webtransport-go-example

Here's a minimal server example to get you going. First, you'll need to create a certificate, as explained in the comments here: https://github.com/GoogleChrome/samples/blob/gh-pages/webtransport/webtransport_server.py

Set up the WebTransport server parameters:

server := &webtransport.Server{
	ListenAddr:     ":4433",
	TLSCert:        webtransport.CertFile{Path: "cert.pem"},
	TLSKey:         webtransport.CertFile{Path: "cert.key"},
	AllowedOrigins: []string{"googlechrome.github.io", "localhost:8000", "new-tab-page"},
}

Then, set up an http.Handler to accept a session, wait for an incoming bidirectional stream from the client, then (in this example) receive data and echo it back:

http.HandleFunc("/counter", func(rw http.ResponseWriter, r *http.Request) {
	session := r.Body.(*webtransport.Session)
	session.AcceptSession()
	defer session.CloseSession()

	// Wait for incoming bidi stream
	s, err := session.AcceptStream()
	if err != nil {
		return
	}

	for {
		buf := make([]byte, 1024)
		n, err := s.Read(buf)
		if err != nil {
			break
		}
		fmt.Printf("Received from bidi stream %v: %s\n", s.StreamID(), buf[:n])
		sendMsg := bytes.ToUpper(buf[:n])
		fmt.Printf("Sending to bidi stream %v: %s\n", s.StreamID(), sendMsg)
		s.Write(sendMsg)
	}
}

Finally, start the server:

ctx, cancel := context.WithCancel(context.Background())
server.Run(ctx)

Here is a simple Chrome browser client to talk to this server. You can open a new browser tab and paste it into the Chrome DevTools console:

let transport = new WebTransport("https://localhost:4433/counter");
await transport.ready;
let stream = await transport.createBidirectionalStream();
let encoder = new TextEncoder();
let decoder = new TextDecoder();
let writer = stream.writable.getWriter();
let reader = stream.readable.getReader();
await writer.write(encoder.encode("Hello, world!"))
console.log(decoder.decode((await reader.read()).value));
transport.close();

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CertFile

type CertFile struct {
	Path string
	Data []byte
}

A CertFile represents a TLS certificate or key, expressed either as a file path or as the certificate/key itself as a []byte.

type QuicConfig

type QuicConfig quic.Config

Wrapper for quic.Config

type ReceiveStream

type ReceiveStream struct {
	quic.ReceiveStream
	// contains filtered or unexported fields
}

ReceiveStream wraps a quic.ReceiveStream providing a unidirectional WebTransport client->server stream, including a Read function.

func (*ReceiveStream) Read

func (s *ReceiveStream) Read(p []byte) (int, error)

Read reads up to len(p) bytes from a WebTransport unidirectional stream, returning the actual number of bytes read.

type SendStream

type SendStream struct {
	quic.SendStream
	// contains filtered or unexported fields
}

SendStream wraps a quic.SendStream providing a unidirectional WebTransport server->client stream, including a Write function.

func (*SendStream) Write

func (s *SendStream) Write(p []byte) (int, error)

Write writes up to len(p) bytes to a WebTransport unidirectional stream, returning the actual number of bytes written.

type Server

type Server struct {
	http.Handler
	// ListenAddr sets an address to bind server to, e.g. ":4433"
	ListenAddr string
	// TLSCert defines a path to, or byte array containing, a certificate (CRT file)
	TLSCert CertFile
	// TLSKey defines a path to, or byte array containing, the certificate's private key (KEY file)
	TLSKey CertFile
	// AllowedOrigins represents list of allowed origins to connect from
	AllowedOrigins []string
	// Additional configuration parameters to pass onto QUIC listener
	QuicConfig *QuicConfig
}

A Server defines parameters for running a WebTransport server. Use http.HandleFunc to register HTTP/3 endpoints for handling WebTransport requests.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Starts a WebTransport server and blocks while it's running. Cancel the supplied Context to stop the server.

type Session

type Session struct {
	quic.Stream
	Session             quic.Session
	ClientControlStream quic.ReceiveStream
	ServerControlStream quic.SendStream
	// contains filtered or unexported fields
}

Session is a WebTransport session (and the Body of a WebTransport http.Request) wrapping the request stream (a quic.Stream), the two control streams and a quic.Session.

func (*Session) AcceptSession

func (s *Session) AcceptSession()

AcceptSession accepts an incoming WebTransport session. Call it in your http.HandleFunc.

func (*Session) AcceptStream

func (s *Session) AcceptStream() (Stream, error)

AcceptStream accepts an incoming (that is, client-initated) bidirectional stream, blocking if necessary until one is available. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call.

func (*Session) AcceptUniStream

func (s *Session) AcceptUniStream(ctx context.Context) (ReceiveStream, error)

AcceptStream accepts an incoming (that is, client-initated) unidirectional stream, blocking if necessary until one is available. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call.

func (*Session) CloseSession

func (s *Session) CloseSession()

CloseSession cleanly closes a WebTransport session. All active streams are cancelled before terminating the session.

func (*Session) CloseWithError

func (s *Session) CloseWithError(code quic.ApplicationErrorCode, str string)

CloseWithError closes a WebTransport session with a supplied error code and string.

func (*Session) Context

func (s *Session) Context() context.Context

Context returns the context for the WebTransport session.

func (*Session) OpenStream

func (s *Session) OpenStream() (Stream, error)

OpenStream creates an outgoing (that is, server-initiated) bidirectional stream. It returns immediately.

func (*Session) OpenStreamSync

func (s *Session) OpenStreamSync(ctx context.Context) (Stream, error)

OpenStream creates an outgoing (that is, server-initiated) bidirectional stream. It generally returns immediately, but if the session's maximum number of streams has been exceeded, it will block until a slot is available. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call.

func (*Session) OpenUniStream

func (s *Session) OpenUniStream() (SendStream, error)

OpenUniStream creates an outgoing (that is, server-initiated) bidirectional stream. It returns immediately.

func (*Session) OpenUniStreamSync

func (s *Session) OpenUniStreamSync(ctx context.Context) (SendStream, error)

OpenUniStreamSync creates an outgoing (that is, server-initiated) unidirectional stream. It generally returns immediately, but if the session's maximum number of streams has been exceeded, it will block until a slot is available. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call.

func (*Session) ReceiveMessage

func (s *Session) ReceiveMessage(ctx context.Context) ([]byte, error)

ReceiveMessage returns a datagram received from a WebTransport session, blocking if necessary until one is available. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call. Note that datagrams are unreliable - depending on network conditions, datagrams sent by the client may never be received by the server.

func (*Session) RejectSession

func (s *Session) RejectSession(errorCode int)

AcceptSession rejects an incoming WebTransport session, returning the supplied HTML error code to the client. Call it in your http.HandleFunc.

func (*Session) SendMessage

func (s *Session) SendMessage(msg []byte) error

SendMessage sends a datagram over a WebTransport session. Supply your own context, or use the WebTransport session's Context() so that ending the WebTransport session automatically cancels this call. Note that datagrams are unreliable - depending on network conditions, datagrams sent by the server may never be received by the client.

type Stream

type Stream quic.Stream

Stream wraps a quic.Stream providing a bidirectional server<->client stream, including Read and Write functions.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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