Skip to content

Commit

Permalink
Merge pull request #15 from GallaFrancesco/multiplexing_pull
Browse files Browse the repository at this point in the history
Stream Multiplexing and Flow Control (updated)
  • Loading branch information
s-ludwig committed Jun 15, 2019
2 parents 7b733b2 + 99575ce commit b9678d3
Show file tree
Hide file tree
Showing 17 changed files with 1,511 additions and 435 deletions.
7 changes: 7 additions & 0 deletions examples/http2/dub.sdl
@@ -0,0 +1,7 @@
name "http2-example"

dependency "vibe-http" path="../../../vibe-http"

/*versions "VibeForceALPN"*/
targetType "executable"
/*buildOptions "profileGC"*/
20 changes: 20 additions & 0 deletions examples/http2/server.crt
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDUjCCAjoCCQDPHygOhe1ZVjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJE
RTEbMBkGA1UECBMSU2NobGVzd2lnLUhvbHN0ZWluMRAwDgYDVQQHFAdMw7xiZWNr
MRkwFwYDVQQKExBvdXRlcnByb2R1Y3Qub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3Qw
HhcNMTIwMjE0MjAxNDM0WhcNMTMwMjEzMjAxNDM0WjBrMQswCQYDVQQGEwJERTEb
MBkGA1UECBMSU2NobGVzd2lnLUhvbHN0ZWluMRAwDgYDVQQHFAdMw7xiZWNrMRkw
FwYDVQQKExBvdXRlcnByb2R1Y3Qub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4fLPWRShlq+o6vM5nhUCtR+IN
mlAlQCzM3asWpQ76T/MOX1Ci1brypRgOXCmYI8c5lFGOIMpH1ZAh987t4UdkwVCn
76pXv5yY4U1jJ5i9v7R7IUeKZ7utwlq1jPo3WWhiZwm1o6AxVRHGiUQ5XrcX0Rgt
0bh9/5BWHLks3OJ44myjpgGv2J7n1LcyecRAd+suN4qsBiTITQBYq27WYsDCqrVB
BoyCPhb51xTlaMlcAH2ekrm886FlC95VrwV7o4jb4sRGYdfKqT6HwXK1yTB3n742
2WavDgRj2vNQaL+XoPiBOrQZ8fj9PeWCyefSEtqz8/dpnJO+pxidKAow8As5AgMB
AAEwDQYJKoZIhvcNAQEFBQADggEBAA8N27Wb9aq9JkAEGZ0Z/CLHbLeV3UQ5qb9V
6KG8vvSoew3oxMlcHa49kq89AKx/gewt4fqCAHk64qpx8aEdbYorTbo9VIbwoLek
9Lyp+AynmqDA6zk5+uOtPkwfN84f30khH04ouSmOvbV+uqD9bVZtR8ULTzbuE2h1
jbT64JQd+GW/uLQ770EKDVFml52BMJWrRFFgaRQhkm9k8krKqsCvYfMoULk3EQqS
b5a/5q9pirXB7AHmiYAnqDu2xQL8N5e548RTZSWNl7mZD1NvVYc4l8tSKfCC3zG3
4wKngPl6t68pdnli67lX3YDTdHgOZWL+CsJT9TIRAOojfp5kWGY=
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions examples/http2/server.key
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuHyz1kUoZavqOrzOZ4VArUfiDZpQJUAszN2rFqUO+k/zDl9Q
otW68qUYDlwpmCPHOZRRjiDKR9WQIffO7eFHZMFQp++qV7+cmOFNYyeYvb+0eyFH
ime7rcJatYz6N1loYmcJtaOgMVURxolEOV63F9EYLdG4ff+QVhy5LNzieOJso6YB
r9ie59S3MnnEQHfrLjeKrAYkyE0AWKtu1mLAwqq1QQaMgj4W+dcU5WjJXAB9npK5
vPOhZQveVa8Fe6OI2+LERmHXyqk+h8Fytckwd5++Ntlmrw4EY9rzUGi/l6D4gTq0
GfH4/T3lgsnn0hLas/P3aZyTvqcYnSgKMPALOQIDAQABAoIBABdkiJEk18h8kgi8
pBdwSBEwyjMbXAo9JvEbMnR+nXWT6afq4hijrT7TPEel3AhUkRB2BBlXgw60v7/u
4ig7pofaE1YYB6t0unCQMPXfsXht9H6ga6fbG2se982JgLi/94JyukJz6v4WYVih
UytLHUBB3SUCMLiZTT3+CmTr5TOamtcUi9ZNKtbxT2l+8iDbi7WaCUznrODZ8YMn
/5RhE3XopBuCmeT8P7EhJSHe1THEcCcXtlChb7bMU7DMpvWYp9XBM4CQ2PpdHN5x
bvKP8P6IH0TpjdBrdE16yO9P14saJoy1GSOx8y6zUxTMdgyFUnXK4bC+nUi3VRrq
cikR3GECgYEA9MUsGAhVF8CRUsgvbn/q1QM1oFG1Dj1+LYEnXXNbdOugZVj2L8eT
olAFFjprciemADmqhnxNgM0Ev+RVZzSDZFrAhQ+NYKw6JQe/EfIvnGyt5GXnoIJQ
4hsibRH2UDHoGzxWTwcxbOdM3mqnY3WCMA3JWoLDEqApGRDh/hFFYtUCgYEAwPOB
Ryfx5wTg/HM2kaVETgPkML81PvGdJxgKDW6XabEVdqf3Ds1UfELHRCK0LH0RS+8L
5GDBuBeAuhpXtzkmwK30rTMfVqxi7IlddcYpBqCg9bHdyg6iAI2htwm+j9aLSCPw
uNeCSqIYKIvLrcvrFWOlLRYonSxlyj7XLFypkNUCgYAnjkGs9JPDzePuS9mWcueh
Wu5spSesUHW2ptuUt5K9F2MJXdITMJ6EKYhY6kH45b1m5erP5wCjYv50gFLo5cyi
CCR6nGPNjqeq2lCfdtMI5WtIsMs43jZyA86Rb8itdxM6a4rLJK9xGQQMIZJBeXj7
iQ7UKLObq/RYT6kl5OagrQKBgGLsdRtGH3+RwMetSgzh7mMRG6ziWyoqNagVaxH3
4SkO4TI0azXrj6Ull4QXRsiIVpXXuQEdmjQH2LeRSedmJbgjd45U53xIZW9f/cqk
DeSX9e4BgvRVDDm8Y2y0Uj7sf/w8cO5Tjzk0Ya5n/cTdB2mv7L9w3OG4IXfPQAI+
f7EBAoGBAKgvIg/bvF705Lg2bebjR2MVeVySDZP/SjY5my+4cF8VSJOK+8WA1MeJ
dv4sukGjAMrMApegKmaiIngb2izINfanEd0xn4h1nCwVcRaNr9fSkZDBhi+JErol
0janntvmByXPD0BXguza7NVJGmtzCyZk+rOHtC1J+qG7kdXHpPYT
-----END RSA PRIVATE KEY-----
92 changes: 92 additions & 0 deletions examples/http2/source/app.d
@@ -0,0 +1,92 @@
/* ==== Vibe.d HTTP/2 Webserver Example ==== */
/* Supports both HTTP and HTTPS transport */
/* Transparent (WIP: exposing settings) */
/* ========================================= */

import vibe.http.server;
import vibe.stream.tls;
import vibe.http.internal.http2.http2 : http2Callback; // ALPN negotiation
import vibe.core.core : runApplication;

/* ==== declare two handlers (could use the same one) ==== */
void handleReq(HTTPServerRequest req, HTTPServerResponse res)
@safe {
if (res.httpVersion == HTTPVersion.HTTP_2)
res.writeBody("Hello, you connected to "~req.path~"! This response is sent through HTTP/2\n");
else
res.writeBody("Hello, World! You connected through HTTP/1, try using HTTP/2!\n");
}

void tlsHandleReq(HTTPServerRequest req, HTTPServerResponse res)
@safe {
if (req.httpVersion == HTTPVersion.HTTP_2)
res.writeBody("Hello, you connected to "~req.path~"! This response is sent through HTTP/2 with TLS\n");
else
res.writeBody("Hello, World! You connected through HTTP/1 with TLS, try using HTTP/2!\n");
}

// sends a very big data frame
void bigHandleReq(size_t DIM)(HTTPServerRequest req, HTTPServerResponse res)
@trusted {
import vibe.utils.array : FixedAppender;
import std.range : iota;

FixedAppender!(immutable(char)[], DIM) appender;

if (req.path == "/") {
foreach(i; iota(1,DIM-4)) appender.put('1');
appender.put(['O','k','!', '\n']);
res.writeBody(appender.data);
}
}

void main()
{
//import vibe.core.log;
//setLogLevel(LogLevel.trace);

/* ==== cleartext HTTP/2 support (h2c) ==== */
auto settings = HTTPServerSettings();
settings.port = 8090;
settings.bindAddresses = ["127.0.0.1"];
listenHTTP!handleReq(settings);

/* ==== cleartext HTTP/2 support (h2c) with a heavy DATA frame ==== */
auto bigSettings = HTTPServerSettings();
settings.port = 8092;
settings.bindAddresses = ["127.0.0.1"];
listenHTTP!(bigHandleReq!100000)(settings);

/* ========== HTTPS (h2) support ========== */
HTTPServerSettings tlsSettings;
tlsSettings.port = 8091;
tlsSettings.bindAddresses = ["127.0.0.1"];

/// setup TLS context by using cert and key in example rootdir
tlsSettings.tlsContext = createTLSContext(TLSContextKind.server);
tlsSettings.tlsContext.useCertificateChainFile("server.crt");
tlsSettings.tlsContext.usePrivateKeyFile("server.key");

// set alpn callback to support HTTP/2 protocol negotiation
tlsSettings.tlsContext.alpnCallback(http2Callback);
listenHTTP!tlsHandleReq(tlsSettings);

/* ========== HTTPS (h2) support with a heavy DATA frame ========== */
HTTPServerSettings bigTLSSettings;
bigTLSSettings.port = 8093;
bigTLSSettings.bindAddresses = ["127.0.0.1"];

/// setup TLS context by using cert and key in example rootdir
bigTLSSettings.tlsContext = createTLSContext(TLSContextKind.server);
bigTLSSettings.tlsContext.useCertificateChainFile("server.crt");
bigTLSSettings.tlsContext.usePrivateKeyFile("server.key");

// set alpn callback to support HTTP/2 protocol negotiation
bigTLSSettings.tlsContext.alpnCallback(http2Callback);
auto l = listenHTTP!(bigHandleReq!100000)(bigTLSSettings);
scope(exit) l.stopListening();

/* ========== Run both `listenHTTP` handlers ========== */
// UNCOMMENT to run
runApplication();
}
59 changes: 41 additions & 18 deletions source/vibe/http/internal/http1.d
Expand Up @@ -186,6 +186,7 @@ private bool originalHandleRequest(InterfaceProxy!Stream http_stream, TCPConnect
else if (is(typeof(http_stream) : TLSStream))
req.clientCertificate = http_stream.extract!TLSStreamType.peerCertificate;
else
// TODO fix: no client certificate
assert(false);
}
}
Expand Down Expand Up @@ -230,7 +231,18 @@ private bool originalHandleRequest(InterfaceProxy!Stream http_stream, TCPConnect
}

// basic request parsing
parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize);
uint h2 = parseRequestHeader(req, reqReader, request_allocator, settings.maxRequestHeaderSize);
if(h2) {
// start http/2 with prior knowledge
uint len = 22 - h2;
ubyte[] dummy; dummy.length = len;

http_stream.read(dummy); // finish reading connection preface
auto h2settings = HTTP2Settings();
auto h2context = HTTP2ServerContext(listen_info, h2settings);
handleHTTP2Connection(tcp_connection, tcp_connection, h2context, true);
return true;
}

logTrace("Got request header.");

Expand Down Expand Up @@ -335,7 +347,6 @@ private bool originalHandleRequest(InterfaceProxy!Stream http_stream, TCPConnect

// handle the request
logTrace("handle request (body %d)", req.bodyReader.leastSize);
res.httpVersion = req.httpVersion;

/**
* UPGRADE TO HTTP/2 for cleartext HTTP/1
Expand All @@ -344,41 +355,53 @@ private bool originalHandleRequest(InterfaceProxy!Stream http_stream, TCPConnect
* "h2" is ignored since it is used for TLS protocol switching (ALPN)
*/
if(req.headers.get("Upgrade") == "h2c" ) {
import vibe.stream.memory;
import vibe.http.internal.http2.exchange;

// write the original response to a buffer
void createResBuffer(IAllocator alloc, ref HTTP2ServerContext ctx) @trusted
string createResBuffer(ref MemoryOutputStream buf, ref size_t hlen) @trusted
{
import vibe.stream.memory;
import vibe.http.internal.http2.exchange;
MemoryOutputStream buf = createMemoryOutputStream(alloc);

res.bodyWriterH2(buf);
res.bodyWriterH2(buf, true);
auto statusLine = (cast(string)buf.data).split("\r\n")[0];
auto hlen = buf.data.length;
ctx.resFrame = buildHeaderFrame!(StartLine.RESPONSE)(statusLine, res.headers, ctx, alloc);
if(req.method != HTTPMethod.HEAD) {
request_task(req, res);
ctx.resBody = buf.data[hlen..$].nullable;
hlen = buf.data.length;

// write body to buffer
request_task(req, res);

// no matching path in handler
if(buf.data.length == hlen && req.method != HTTPMethod.HEAD) {
return "HTTP/2 404 Not Found\r\n";
}

//// matching path, data needs to be saved
//if(req.method != HTTPMethod.HEAD) {
//ctx.resBody = request_allocator.make!(ubyte)(buf.data[hlen..$]);
//}

return statusLine;
}

auto psettings = "HTTP2-Settings" in req.headers;
enforceHTTP(psettings !is null, HTTPStatus.badRequest, "Upgrade request must
include HTTP2-Settings");
auto h2settings = *psettings;

logDebug("Switching to HTTP/2");
logInfo("Switching to HTTP/2");
logTrace("handle request (body %d)", req.bodyReader.leastSize);

// initialize the request handler
auto h2context = HTTP2ServerContext(listen_info);
h2context.setNoTLS;
createResBuffer(request_allocator, h2context);

MemoryOutputStream buf = createMemoryOutputStream(request_allocator);
size_t hlen;
auto st = createResBuffer(buf, hlen);
auto switchRes = HTTPServerResponse(http_stream, cproxy, settings, request_allocator);

return startHTTP2Connection(tcp_connection, h2settings, h2context, switchRes);
return startHTTP2Connection(tcp_connection, h2settings, h2context, switchRes,
res.headers, st, request_allocator, buf.data[hlen..$]);
}


res.httpVersion = req.httpVersion;
request_task(req, res);


Expand Down
84 changes: 84 additions & 0 deletions source/vibe/http/internal/http2/error.d
@@ -0,0 +1,84 @@
module vibe.http.internal.http2.error;

import vibe.http.internal.http2.hpack.exception;
import vibe.http.internal.http2.frame;

import vibe.core.log;
import vibe.core.net;
import vibe.core.core;
import vibe.core.stream;
import vibe.stream.tls;
import vibe.internal.array;
import vibe.internal.allocator;
import vibe.internal.freelistref;
import vibe.internal.interfaceproxy;

import std.range;
import std.base64;
import std.traits;
import std.bitmanip; // read from ubyte (decoding)
import std.typecons;
import std.conv : to;
import std.exception;
import std.algorithm : canFind; // alpn callback
import std.algorithm.iteration;

enum HTTP2Error {
NO_ERROR = 0x0,
PROTOCOL_ERROR = 0x1,
INTERNAL_ERROR = 0x2,
FLOW_CONTROL_ERROR = 0x3,
SETTINGS_TIMEOUT = 0x4,
STREAM_CLOSED = 0x5,
FRAME_SIZE_ERROR = 0x6,
REFUSED_STREAM = 0x7,
CANCEL = 0x8,
COMPRESSION_ERROR = 0x9,
CONNECT_ERROR = 0xa,
ENHANCE_YOUR_CALM = 0xb,
INADEQUATE_SECURITY = 0xc,
HTTP_1_1_REQUIRED = 0xd
}

enum GOAWAYFrameLength = 17;

/// creates a GOAWAY frame as defined in RFC 7540, section 6.8
void buildGOAWAYFrame(R)(ref R buf, const uint streamId, HTTP2Error error)
@safe @nogc
{
assert(buf.length == GOAWAYFrameLength, "Unable to create GOAWAY frame");

// last stream processed by the server (client-initiated)
uint sid = (streamId > 1) ? streamId - 2 : 0;

buf.createHTTP2FrameHeader(8, HTTP2FrameType.GOAWAY, 0x0, 0x0);
buf.putBytes!4(sid & 127); // last stream ID
buf.putBytes!4(error);
}
/// ditto
auto buildGOAWAYFrame(uint sid, HTTP2Error code) @safe
{
BatchBuffer!(ubyte, GOAWAYFrameLength) gbuf;

gbuf.putN(GOAWAYFrameLength);
gbuf.buildGOAWAYFrame(sid, code);

return gbuf.peekDst;
}


/// exceptions
T enforceHTTP2(T)(T condition, string message = null, HTTP2Error h2e = HTTP2Error.NO_ERROR, string file = __FILE__, typeof(__LINE__) line = __LINE__) @trusted
{
return enforce(condition, new HTTP2Exception(message, h2e, file, line));
}

class HTTP2Exception : Exception
{
HTTP2Error code;

this(string msg, HTTP2Error h2e = HTTP2Error.NO_ERROR, string file = __FILE__, size_t line = __LINE__) {
code = h2e;
super(msg, file, line);
}
}

0 comments on commit b9678d3

Please sign in to comment.