blob: c0ec7bd049c2980a81c6c617ef58d2b63ee57850 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'src/connection.dart';
import 'src/hpack/hpack.dart' show Header;
export 'src/hpack/hpack.dart' show Header;
typedef void ActiveStateHandler(bool isActive);
/// Settings for a [TransportConnection].
abstract class Settings {
/// The maximum number of concurrent streams the remote end can open
/// (defaults to being unlimited).
final int concurrentStreamLimit;
/// The default stream window size the remote peer can use when creating new
/// streams (defaults to 65535 bytes).
final int streamWindowSize;
const Settings({this.concurrentStreamLimit, this.streamWindowSize});
}
/// Settings for a [TransportConnection] a server can make.
class ServerSettings extends Settings {
const ServerSettings({int concurrentStreamLimit, int streamWindowSize})
: super(
concurrentStreamLimit: concurrentStreamLimit,
streamWindowSize: streamWindowSize);
}
/// Settings for a [TransportConnection] a client can make.
class ClientSettings extends Settings {
/// Whether the client allows pushes from the server (defaults to false).
final bool allowServerPushes;
const ClientSettings(
{int concurrentStreamLimit,
int streamWindowSize,
this.allowServerPushes: false})
: super(
concurrentStreamLimit: concurrentStreamLimit,
streamWindowSize: streamWindowSize);
}
/// Represents a HTTP/2 connection.
abstract class TransportConnection {
/// Pings the other end.
Future ping();
/// Sets the active state callback.
///
/// This callback is invoked with `true` when the number of active streams
/// goes from 0 to 1 (the connection goes from idle to active), and with
/// `false` when the number of active streams becomes 0 (the connection goes
/// from active to idle).
set onActiveStateChanged(ActiveStateHandler callback);
/// Finish this connection.
///
/// No new streams will be accepted or can be created.
Future finish();
/// Terminates this connection forcefully.
Future terminate();
}
abstract class ClientTransportConnection extends TransportConnection {
factory ClientTransportConnection.viaSocket(Socket socket,
{ClientSettings settings}) =>
new ClientTransportConnection.viaStreams(socket, socket,
settings: settings);
factory ClientTransportConnection.viaStreams(
Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
{ClientSettings settings}) {
if (settings == null) settings = const ClientSettings();
return new ClientConnection(incoming, outgoing, settings);
}
/// Whether this connection is open and can be used to make new requests
/// via [makeRequest].
bool get isOpen;
/// Creates a new outgoing stream.
ClientTransportStream makeRequest(List<Header> headers,
{bool endStream: false});
}
abstract class ServerTransportConnection extends TransportConnection {
factory ServerTransportConnection.viaSocket(Socket socket,
{ServerSettings settings}) {
return new ServerTransportConnection.viaStreams(socket, socket,
settings: settings);
}
factory ServerTransportConnection.viaStreams(
Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
{ServerSettings settings:
const ServerSettings(concurrentStreamLimit: 1000)}) {
if (settings == null) settings = const ServerSettings();
return new ServerConnection(incoming, outgoing, settings);
}
/// Incoming HTTP/2 streams.
Stream<ServerTransportStream> get incomingStreams;
}
/// Represents a HTTP/2 stream.
abstract class TransportStream {
/// The id of this stream.
///
/// * odd numbered streams are client streams
/// * even numbered streams are opened from the server
int get id;
/// A stream of data and/or headers from the remote end.
Stream<StreamMessage> get incomingMessages;
/// A sink for writing data and/or headers to the remote end.
StreamSink<StreamMessage> get outgoingMessages;
/// Sets the termination handler on this stream.
///
/// The handler will be called if the stream receives an RST_STREAM frame.
set onTerminated(void value(int v));
/// Terminates this HTTP/2 stream in an un-normal way.
///
/// For normal termination, one can cancel the [StreamSubscription] from
/// `incoming.listen()` and close the `outgoing` [StreamSink].
///
/// Terminating this HTTP/2 stream will free up all resources associated with
/// it locally and will notify the remote end that this stream is no longer
/// used.
void terminate();
// For convenience only.
void sendHeaders(List<Header> headers, {bool endStream: false}) {
outgoingMessages
.add(new HeadersStreamMessage(headers, endStream: endStream));
if (endStream) outgoingMessages.close();
}
void sendData(List<int> bytes, {bool endStream: false}) {
outgoingMessages.add(new DataStreamMessage(bytes, endStream: endStream));
if (endStream) outgoingMessages.close();
}
}
abstract class ClientTransportStream extends TransportStream {
/// Streams which the remote end pushed to this endpoint.
///
/// If peer pushes were enabled, the client is responsible to either
/// handle or reject any peer push.
Stream<TransportStreamPush> get peerPushes;
}
abstract class ServerTransportStream extends TransportStream {
/// Whether a method to [push] will succeed. Requirements for this getter to
/// return `true` are:
/// * this stream must be in the Open or HalfClosedRemote state
/// * the client needs to have the "enable push" settings enabled
/// * the number of active streams has not reached the maximum
bool get canPush;
/// Pushes a new stream to the remote peer.
ServerTransportStream push(List<Header> requestHeaders);
}
/// Represents a message which can be sent over a HTTP/2 stream.
abstract class StreamMessage {
final bool endStream;
StreamMessage({bool endStream}) : this.endStream = endStream ?? false;
}
/// Represents a data message which can be sent over a HTTP/2 stream.
class DataStreamMessage extends StreamMessage {
final List<int> bytes;
DataStreamMessage(this.bytes, {bool endStream}) : super(endStream: endStream);
String toString() => 'DataStreamMessage(${bytes.length} bytes)';
}
/// Represents a headers message which can be sent over a HTTP/2 stream.
class HeadersStreamMessage extends StreamMessage {
final List<Header> headers;
HeadersStreamMessage(this.headers, {bool endStream})
: super(endStream: endStream);
String toString() => 'HeadersStreamMessage(${headers.length} headers)';
}
/// Represents a remote stream push.
class TransportStreamPush {
/// The request headers which [stream] is the response to.
final List<Header> requestHeaders;
/// The remote stream push.
final ClientTransportStream stream;
TransportStreamPush(this.requestHeaders, this.stream);
String toString() =>
'TransportStreamPush(${requestHeaders.length} request headers headers)';
}
/// An exception thrown by the HTTP/2 implementation.
class TransportException implements Exception {
final String message;
TransportException(this.message);
String toString() => 'HTTP/2 error: $message';
}
/// An exception thrown when a HTTP/2 connection error occurred.
class TransportConnectionException extends TransportException {
final int errorCode;
TransportConnectionException(int errorCode, String details)
: errorCode = errorCode,
super('Connection error: $details (errorCode: $errorCode)');
}
/// An exception thrown when a HTTP/2 stream error occured.
class StreamTransportException extends TransportException {
StreamTransportException(String details) : super('Stream error: $details');
}