/// <summary> /// Process cookies. /// </summary> /// <param name="toProcess">The header list containing the cookies.</param> private void ProcessCookie(HeadersList toProcess) { /* 12 -> 8.1.3.4. * If there are multiple Cookie header fields after * decompression, these MUST be concatenated into a single octet string * using the two octet delimiter of 0x3B, 0x20 (the ASCII string "; "). */ const string delimiter = "; "; var cookie = new StringBuilder(String.Empty); for (int i = 0; i < toProcess.Count; i++) { if (!toProcess[i].Key.Equals(CommonHeaders.Cookie)) { continue; } cookie.Append(toProcess[i].Value); cookie.Append(delimiter); toProcess.RemoveAt(i--); } if (cookie.Length > 0) { // Add without last delimeter toProcess.Add(new KeyValuePair <string, string>(CommonHeaders.Cookie, cookie.ToString(cookie.Length - 2, 2))); } }
public void ClearReferenceSet() { var bytes = new byte[] { 0x30 }; // this value depends on pattern var clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get") }; var clientCompressionProc = new CompressionProcessor(); var serverCompressionProc = new CompressionProcessor(); serverCompressionProc.Decompress(clientCompressionProc.Compress(clientHeaders)); stateBefore = new CompProcState(serverCompressionProc); Assert.True(stateBefore.LocalRefSet.Count > 0); serverCompressionProc.Decompress(bytes); stateAfter = new CompProcState(serverCompressionProc); serverCompressionProc.Dispose(); clientCompressionProc.Dispose(); Assert.True(stateAfter.LocalRefSet == null || stateAfter.LocalRefSet.Count == 0); }
public void HeadersList() { var collection = new HeadersList(new[] { new KeyValuePair <string, string>("myKey1", "myValue1"), new KeyValuePair <string, string>("myKey2", "myValue2"), new KeyValuePair <string, string>("myKey3", "myValue3"), new KeyValuePair <string, string>("myKey4", "myValue4"), new KeyValuePair <string, string>("myKey5", "myValue5"), new KeyValuePair <string, string>("myKey6", "myValue6"), new KeyValuePair <string, string>("myKey7", "myValue7"), new KeyValuePair <string, string>("myKey8", "myValue8"), new KeyValuePair <string, string>("myKey9", "myValue9"), new KeyValuePair <string, string>("myKey0", "myValue0") }); Assert.Equal(collection.Count, 10); Assert.Equal(collection.StoredHeadersSize, 60 + 80 + 32 * collection.Count); collection.Add(new KeyValuePair <string, string>("someAddKey1", "someAddValue1")); collection.Add(new KeyValuePair <string, string>("someAddKey2", "someAddValue2")); Assert.Equal(collection.Count, 12); Assert.Equal(collection.StoredHeadersSize, 60 + 80 + 32 * collection.Count + 22 + 26); int headersSize = collection.Sum(header => header.Key.Length + header.Value.Length + 32); Assert.Equal(collection.StoredHeadersSize, headersSize); }
//Incoming internal Http2Stream(HeadersList headers, int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, ICompressionProcessor comprProc, Priority priority = Priority.Pri3) : this(id, writeQueue, flowCrtlManager, comprProc, priority) { Headers = headers; }
/// <summary> /// Adopts protocol terms into owin environment. /// </summary> /// <param name="headers">The headers.</param> /// <returns></returns> private OwinContext PopulateEnvironment(HeadersList headers) { var owinContext = new OwinContext(); var headersAsDict = headers.ToDictionary(header => header.Key, header => new[] {header.Value}, StringComparer.OrdinalIgnoreCase); owinContext.Environment[CommonOwinKeys.RequestHeaders] = headersAsDict; owinContext.Environment[CommonOwinKeys.ResponseHeaders] = new Dictionary<string, string[]>(); var owinRequest = owinContext.Request; var owinResponse = owinContext.Response; owinRequest.Method = headers.GetValue(CommonHeaders.Method); owinRequest.Path = headers.GetValue(CommonHeaders.Path); owinRequest.CallCancelled = CancellationToken.None; owinRequest.Host = headers.GetValue(CommonHeaders.Host); owinRequest.PathBase = String.Empty; owinRequest.QueryString = String.Empty; owinRequest.Body = new MemoryStream(); owinRequest.Protocol = Protocols.Http2; owinRequest.Scheme = headers.GetValue(CommonHeaders.Scheme) == Uri.UriSchemeHttp ? Uri.UriSchemeHttp : Uri.UriSchemeHttps; owinRequest.RemoteIpAddress = _transportInfo.RemoteIpAddress; owinRequest.RemotePort = Convert.ToInt32(_transportInfo.RemotePort); owinRequest.LocalIpAddress = _transportInfo.LocalIpAddress; owinRequest.LocalPort = _transportInfo.LocalPort; owinResponse.Body = new ResponseStream{Capacity = 16384}; return owinContext; }
public void NeverIndexedEmission() { var serverCompressionProc = new CompressionProcessor(); var header = new KeyValuePair <string, string>("custom-key", "custom-value"); byte[] index = { 0x10 }; byte[] name = Encoding.UTF8.GetBytes(header.Key); byte[] nameLength = name.Length.ToUVarInt(7); byte[] value = Encoding.UTF8.GetBytes(header.Value); byte[] valueLength = value.Length.ToUVarInt(7); byte[] encodedHeader = new byte[index.Length + name.Length + value.Length + nameLength.Length + valueLength.Length]; // creates encoded header int offset = 0; Buffer.BlockCopy(index, 0, encodedHeader, 0, index.Length); offset += index.Length; Buffer.BlockCopy(nameLength, 0, encodedHeader, offset, nameLength.Length); offset += nameLength.Length; Buffer.BlockCopy(name, 0, encodedHeader, offset, name.Length); offset += name.Length; Buffer.BlockCopy(valueLength, 0, encodedHeader, offset, valueLength.Length); offset += valueLength.Length; Buffer.BlockCopy(value, 0, encodedHeader, offset, value.Length); HeadersList deserializedHeaders = serverCompressionProc.Decompress(encodedHeader); Assert.Equal(deserializedHeaders[0].Key, header.Key); Assert.Equal(deserializedHeaders[0].Value, header.Value); }
//Outgoing internal Http2Stream(int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority) { if (id <= 0) throw new ArgumentOutOfRangeException("invalid id for stream"); if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority out of range"); _id = id; Priority = priority; _writeQueue = writeQueue; _flowCrtlManager = flowCrtlManager; _unshippedFrames = new Queue<DataFrame>(16); Headers = new HeadersList(); SentDataAmount = 0; ReceivedDataAmount = 0; IsFlowControlBlocked = false; IsFlowControlEnabled = _flowCrtlManager.IsFlowControlEnabled; WindowSize = _flowCrtlManager.StreamsInitialWindowSize; _flowCrtlManager.NewStreamOpenedHandler(this); OnFrameSent += (sender, args) => FramesSent++; }
public static void LogHeaders(HeadersList headers) { Console.WriteLine("Headers set:"); foreach (var header in headers) { Console.WriteLine("{0}: {1}", header.Key, header.Value); } }
/// <summary> /// Writes the status header to the stream. /// </summary> /// <param name="final">if set to <c>true</c> then marks headers frame as final.</param> private void SendHeaders(bool final) { var responseHeaders = new HeadersList(_responseHeaders) { new KeyValuePair <string, string>(CommonHeaders.Status, _owinContext.Response.StatusCode.ToString(CultureInfo.InvariantCulture)) }; _protocolStream.WriteHeadersFrame(responseHeaders, final, true); }
private bool ValidateHeaders(IFormFile file) { using (var stream = new StreamReader(file.OpenReadStream())) { var list = stream.ReadLine()?.Split(','); return(HeadersList.SequenceEqual(list)); } }
private void WriteStatus(Http2Stream stream, int statusCode, bool final) { var headers = new HeadersList { new KeyValuePair <string, string>(":status", statusCode.ToString()), }; stream.WriteHeadersFrame(headers, final); }
public byte[] Compress(HeadersList headers) { var toSend = new HeadersList(); var toDelete = new HeadersList(_remoteRefSet); ClearStream(_serializerStream, (int) _serializerStream.Position); foreach (var header in headers) { if (header.Key == null || header.Value == null) { throw new InvalidHeaderException(header); } if (!_remoteRefSet.Contains(header)) { //Not there, Will send toSend.Add(header); } else { //Already there, don't delete toDelete.Remove(header); } } foreach (var header in toDelete) { //Anything left in toDelete, should send, so it is deleted from ref set. CompressIndexed(header); _remoteRefSet.Remove(header); //Update our copy } foreach (var header in toSend) { //Send whatever was left in headersCopy if (_remoteHeadersTable.Contains(header) || _staticTable.Contains(header)) { CompressIndexed(header); } else { CompressIncremental(header); } _remoteRefSet.Add(header); } _serializerStream.Flush(); var result = new byte[_serializerStream.Position]; var streamBuffer = _serializerStream.GetBuffer(); Buffer.BlockCopy(streamBuffer, 0, result, 0, (int)_serializerStream.Position); return result; }
/// <summary> /// Compress the header list. /// </summary> /// <param name="headers">The headers.</param> /// <returns>The compressed headers.</returns> public byte[] Compress(HeadersList headers) { var toSend = new HeadersList(); var toDelete = new HeadersList(_remoteRefSet); ClearStream(_serializerStream, (int)_serializerStream.Position); foreach (var header in headers) { if (header.Key == null || header.Value == null) { throw new InvalidHeaderException(header); } if (!_remoteRefSet.Contains(header)) { // Not there, Will send toSend.Add(header); } else { // Already there, don't delete toDelete.Remove(header); } } foreach (var header in toDelete) { // Anything left in toDelete, should send, so it is deleted from ref set. CompressIndexed(header); _remoteRefSet.Remove(header); // Update our copy } foreach (var header in toSend) { // Send whatever was left in headersCopy if (_remoteHeadersTable.Contains(header) || Constants.StaticTable.Contains(header)) { CompressIndexed(header); } else { CompressIncremental(header); } _remoteRefSet.Add(header); } _serializerStream.Flush(); var result = new byte[_serializerStream.Position]; var streamBuffer = _serializerStream.GetBuffer(); Buffer.BlockCopy(streamBuffer, 0, result, 0, (int)_serializerStream.Position); return(result); }
//Incoming internal Http2Stream(HeadersList headers, int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, ICompressionProcessor comprProc, int priority = Constants.DefaultStreamPriority) : this(id, writeQueue, flowCrtlManager, comprProc, priority) { if (headers == null) throw new ArgumentNullException("cannot create stream with null headers"); Headers = headers; }
/// <summary> /// Writes the status header to the stream. /// </summary> /// <param name="stream">The stream.</param> /// <param name="statusCode">The status code.</param> /// <param name="final">if set to <c>true</c> then marks headers frame as final.</param> /// <param name="headers">Additional headers</param> private void WriteStatus(Http2Stream stream, int statusCode, bool final, HeadersList headers = null) { if (headers == null) { headers = new HeadersList(); } headers.Add(new KeyValuePair <string, string>(CommonHeaders.Status, statusCode.ToString(CultureInfo.InvariantCulture))); stream.WriteHeadersFrame(headers, final, true); }
public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_wereFirstSettingsSent) { _session.SendRequest(pairs, priority, isEndStream); } else { OnFirstSettingsSent += (o, args) => _session.SendRequest(pairs, priority, isEndStream); } }
public CompProcState(CompressionProcessor proc) { IsSettingHeaderTableSizeReceived = (bool)GetFieldValue(typeof(CompressionProcessor), "_isSettingHeaderTableSizeReceived", proc); SettingsHeaderTableSize = (int)GetFieldValue(typeof(CompressionProcessor), "_settingsHeaderTableSize", proc); MaxHeaderByteSize = (int)GetFieldValue(typeof(CompressionProcessor), "_maxHeaderByteSize", proc); LocalRefSet = (HeadersList)GetFieldValue(typeof(CompressionProcessor), "_localRefSet", proc); }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); Http2Logger.LogDebug("New continuation with id = " + contFrame.StreamId); if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with id = 0"); } var serHeaders = new byte[contFrame.CompressedHeaders.Count]; Buffer.BlockCopy(contFrame.CompressedHeaders.Array, contFrame.CompressedHeaders.Offset, serHeaders, 0, serHeaders.Length); var decomprHeaders = _comprProc.Decompress(serHeaders); var contHeaders = new HeadersList(decomprHeaders); foreach (var header in contHeaders) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value); } contFrame.Headers.AddRange(contHeaders); var sequence = _headersSequences.Find(seq => seq.StreamId == contFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(contFrame.StreamId, contFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(contFrame); } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(contFrame.StreamId); if (stream == null) { stream = CreateStream(sequence.Headers, contFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
public PushPromiseFrame(Int32 streamId, Int32 promisedStreamId, bool isEndPushPromise, HeadersList headers = null) : base(new byte[HeadersOffset]) { Contract.Assert(streamId > 0 && promisedStreamId > 0); StreamId = streamId; FrameType = FrameType.PushPromise; FrameLength = Buffer.Length - Constants.FramePreambleSize; PromisedStreamId = promisedStreamId; Headers = headers ?? new HeadersList(); IsEndPushPromise = isEndPushPromise; }
public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_wereFirstSettingsSent) { _session.SendRequest(pairs, priority, isEndStream); } else { OnFirstSettingsSent += (o, args) => { //unsec handled via upgrade handshake if (_isSecure) _session.SendRequest(pairs, priority, isEndStream); }; } }
private void EvictHeaderTableEntries(HeadersList headersTable, HeadersList refTable) { /* 07 -> 3.3.2 Whenever the maximum size for the header table is made smaller, entries are evicted from the end of the header table until the size of the header table is less than or equal to the maximum size. */ while (headersTable.StoredHeadersSize >= _maxHeaderByteSize && headersTable.Count > 0) { var header = headersTable[headersTable.Count - 1]; headersTable.RemoveAt(headersTable.Count - 1); /* 07 -> 3.3.2 Whenever an entry is evicted from the header table, any reference to that entry contained by the reference set is removed. */ if (refTable.Contains(header)) refTable.Remove(header); } }
public CompressionProcessor(ConnectionEnd end) { if (end == ConnectionEnd.Client) { _localHeaderTable = CompressionInitialHeaders.ResponseInitialHeaders; _remoteHeaderTable = CompressionInitialHeaders.RequestInitialHeaders; } else { _localHeaderTable = CompressionInitialHeaders.RequestInitialHeaders; _remoteHeaderTable = CompressionInitialHeaders.ResponseInitialHeaders; } _localRefSet = new HeadersList(); _remoteRefSet = new HeadersList(); InitCompressor(); InitDecompressor(); }
public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_wereFirstSettingsSent) { _session.SendRequest(pairs, priority, isEndStream); } else { OnFirstSettingsSent += (o, args) => { //unsec handled via upgrade handshake if (_isSecure) { _session.SendRequest(pairs, priority, isEndStream); } }; } }
/// <summary> /// Header delta compression algorithm. /// </summary> public HeaderCompression() { //The initial value is 4,096 bytes. _maxHeaderByteSize = 4096; _isSettingHeaderTableSizeReceived = false; //The header table is initially empty. _remoteHeadersTable = new HeadersList(); _localHeadersTable = new HeadersList(); _remoteRefSet = new HeadersList(); _localRefSet = new HeadersList(); _huffmanProc = new Processor(); InitCompressor(); InitDecompressor(); }
private static HeadersList GetHeadersList(Uri uri) { const string method = "get"; var path = uri.PathAndQuery; var version = Protocols.Http2; var scheme = uri.Scheme; var host = uri.Host; var pairs = new HeadersList { new KeyValuePair <string, string>(":method", method), new KeyValuePair <string, string>(":path", path), new KeyValuePair <string, string>(":version", version), new KeyValuePair <string, string>(":authority", host + ":" + uri.Port), new KeyValuePair <string, string>(":scheme", scheme), }; return(pairs); }
// for outgoing public PushPromiseFrame(Int32 streamId, Int32 promisedStreamId, bool hasPadding, bool isEndHeaders, HeadersList headers = null) { Contract.Assert(streamId > 0 && promisedStreamId > 0); int preambleLength = Constants.FramePreambleSize + PromisedIdLength; if (hasPadding) { preambleLength += PadHighLowLength; } // construct frame without Headers Block and Padding bytes Buffer = new byte[preambleLength]; /* 12 -> 6.6 * The PUSH_PROMISE frame includes optional padding. Padding fields and * flags are identical to those defined for DATA frames. */ if (hasPadding) { // generate padding var padHigh = (byte)1; var padLow = (byte)new Random().Next(1, 7); HasPadHigh = true; HasPadLow = true; PadHigh = padHigh; PadLow = padLow; } PayloadLength = Buffer.Length - Constants.FramePreambleSize; FrameType = FrameType.PushPromise; StreamId = streamId; PromisedStreamId = promisedStreamId; IsEndHeaders = isEndHeaders; if (headers != null) { Headers = headers; } }
//localPath should be provided only for post and put cmds //serverPostAct should be provided only for post cmd private void SubmitRequest(Uri request, string method) { //Submit request if http2 was chosen Http2Logger.LogConsole("Submitting request"); var headers = new HeadersList { new KeyValuePair <string, string>(CommonHeaders.Method, method.ToLower()), new KeyValuePair <string, string>(CommonHeaders.Path, request.PathAndQuery.ToLower()), new KeyValuePair <string, string>(CommonHeaders.Authority, _host.ToLower()), new KeyValuePair <string, string>(CommonHeaders.Scheme, _scheme.ToLower()), }; Http2Logger.LogHeaders(headers); //Sending request with default priority _sessionAdapter.SendRequest(headers, Constants.DefaultStreamPriority, true); Http2Logger.LogConsole("Request sent"); }
/// <summary> /// Decompress the headers. /// </summary> /// <param name="serializedHeaders">The serialised headers.</param> /// <returns>The header list.</returns> public HeadersList Decompress(byte[] serializedHeaders) { try { _currentOffset = 0; var unindexedHeadersList = new HeadersList(); while (_currentOffset != serializedHeaders.Length) { var entry = ParseHeader(serializedHeaders); // parsed indexed header which was already in the refSet if (entry == null) { continue; } var header = new KeyValuePair <string, string>(entry.Item1, entry.Item2); if (entry.Item3 == IndexationType.WithoutIndexation || entry.Item3 == IndexationType.NeverIndexed) { unindexedHeadersList.Add(header); } } // Base result on already modified reference set var result = new HeadersList(_localRefSet); // Add to result Without indexation and Never Indexed // They were not added into reference set result.AddRange(unindexedHeadersList); ProcessCookie(result); return(result); } catch (Exception e) { throw new Exception(e.Message); } }
public CompressionProcessor(ConnectionEnd end) { //default max headers table size _maxHeaderByteSize = 4096; //05 The header table is initially empty. _remoteHeadersTable = new HeadersList(); _localHeadersTable = new HeadersList(); //05 The reference set is initially empty. _remoteRefSet = new HeadersList(); _localRefSet = new HeadersList(); _huffmanProc = new HuffmanCompressionProcessor(); _localEnd = end; InitCompressor(); InitDecompressor(); }
public CompressionProcessor() { /* 12 -> 6.5.2 * The initial value is 4,096 bytes. */ _maxHeaderByteSize = 4096; _isSettingHeaderTableSizeReceived = false; /* 07 -> 3.1.2 * The header table is initially empty. */ _remoteHeadersTable = new HeadersList(); _localHeadersTable = new HeadersList(); _remoteRefSet = new HeadersList(); _localRefSet = new HeadersList(); _huffmanProc = new HuffmanCompressionProcessor(); InitCompressor(); InitDecompressor(); }
/// <summary> /// Evict header table entry. /// </summary> /// <param name="headersTable"></param> /// <param name="refTable"></param> private void EvictHeaderTableEntries(HeadersList headersTable, HeadersList refTable) { /* 07 -> 3.3.2 * Whenever the maximum size for the header table is made smaller, * entries are evicted from the end of the header table until the size * of the header table is less than or equal to the maximum size. */ while (headersTable.StoredHeadersSize >= _maxHeaderByteSize && headersTable.Count > 0) { var header = headersTable[headersTable.Count - 1]; headersTable.RemoveAt(headersTable.Count - 1); /* 07 -> 3.3.2 * Whenever an entry is evicted from the header table, any reference to * that entry contained by the reference set is removed. */ if (refTable.Contains(header)) { refTable.Remove(header); } } }
//Outgoing internal Http2Stream(int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, ICompressionProcessor comprProc, Priority priority = Priority.Pri3) { _id = id; Priority = priority; _writeQueue = writeQueue; _compressionProc = comprProc; _flowCrtlManager = flowCrtlManager; _unshippedFrames = new Queue<DataFrame>(16); Headers = new HeadersList(); SentDataAmount = 0; ReceivedDataAmount = 0; IsFlowControlBlocked = false; IsFlowControlEnabled = _flowCrtlManager.IsStreamsFlowControlledEnabled; WindowSize = _flowCrtlManager.StreamsInitialWindowSize; _flowCrtlManager.NewStreamOpenedHandler(this); }
public CompressionProcessor() { /* 12 -> 6.5.2 The initial value is 4,096 bytes. */ _maxHeaderByteSize = 4096; _isSettingHeaderTableSizeReceived = false; /* 07 -> 3.1.2 The header table is initially empty. */ _remoteHeadersTable = new HeadersList(); _localHeadersTable = new HeadersList(); _remoteRefSet = new HeadersList(); _localRefSet = new HeadersList(); _huffmanProc = new HuffmanCompressionProcessor(); InitCompressor(); InitDecompressor(); }
protected static Http2Stream SubmitRequest(Http2Session session, Uri uri) { const string method = "get"; string path = uri.PathAndQuery; string version = Protocols.Http2; string scheme = uri.Scheme; string host = uri.Host; var pairs = new HeadersList { new KeyValuePair <string, string>(":method", method), new KeyValuePair <string, string>(":path", path), new KeyValuePair <string, string>(":version", version), new KeyValuePair <string, string>(":host", host), new KeyValuePair <string, string>(":scheme", scheme), }; session.SendRequest(pairs, Priority.None, false); return(session.ActiveStreams[1]); }
/// <summary> /// Insert to headers table. /// </summary> /// <param name="header">The header.</param> /// <param name="refSet">The refernced header list.</param> /// <param name="headersTable">The header list.</param> private void InsertToHeadersTable(KeyValuePair <string, string> header, HeadersList refSet, HeadersList headersTable) { /* 07 -> 3.3.1 * The size of an entry is the sum of its name's length in octets (as * defined in Section 4.1.2), of its value's length in octets * (Section 4.1.2) and of 32 octets. */ int headerLen = header.Key.Length + header.Value.Length + 32; /* 07 -> 3.3.2 * Whenever a new entry is to be added to the table, any name referenced * by the representation of this new entry is cached, and then entries * are evicted from the end of the header table until the size of the * header table is less than or equal to (maximum size - new entry * size), or until the table is empty. * * If the size of the new entry is less than or equal to the maximum * size, that entry is added to the table. It is not an error to * attempt to add an entry that is larger than the maximum size. */ while (headersTable.StoredHeadersSize + headerLen >= _maxHeaderByteSize && headersTable.Count > 0) { headersTable.RemoveAt(headersTable.Count - 1); /* 07 -> 3.3.2 * Whenever an entry is evicted from the header table, any reference to * that entry contained by the reference set is removed. */ if (refSet.Contains(header)) { refSet.Remove(header); } } /* 07 -> 3.2.1 * We should always insert into * begin of the headers table. */ headersTable.Insert(0, header); }
//localPath should be provided only for post and put cmds //serverPostAct should be provided only for post cmd private void SubmitRequest(Uri request, string method, string localPath = null, string serverPostAct = null) { var headers = new HeadersList { new KeyValuePair <string, string>(":method", method), new KeyValuePair <string, string>(":path", request.PathAndQuery), new KeyValuePair <string, string>(":version", _version), new KeyValuePair <string, string>(":host", _host), new KeyValuePair <string, string>(":scheme", _scheme), }; if (!String.IsNullOrEmpty(localPath)) { headers.Add(new KeyValuePair <string, string>(":localPath".ToLower(), localPath)); } if (!String.IsNullOrEmpty(serverPostAct)) { headers.Add(new KeyValuePair <string, string>(":serverPostAct".ToLower(), serverPostAct)); } //Sending request with average priority _clientSession.SendRequest(headers, Priority.None, false); }
// for outgoing public PushPromiseFrame(Int32 streamId, Int32 promisedStreamId, bool hasPadding, bool isEndHeaders, HeadersList headers = null) { Contract.Assert(streamId > 0 && promisedStreamId > 0); int preambleLength = Constants.FramePreambleSize + PromisedIdLength; if (hasPadding) preambleLength += PadHighLowLength; // construct frame without Headers Block and Padding bytes Buffer = new byte[preambleLength]; /* 12 -> 6.6 The PUSH_PROMISE frame includes optional padding. Padding fields and flags are identical to those defined for DATA frames. */ if (hasPadding) { // generate padding var padHigh = (byte)1; var padLow = (byte)new Random().Next(1, 7); HasPadHigh = true; HasPadLow = true; PadHigh = padHigh; PadLow = padLow; } PayloadLength = Buffer.Length - Constants.FramePreambleSize; FrameType = FrameType.PushPromise; StreamId = streamId; PromisedStreamId = promisedStreamId; IsEndHeaders = isEndHeaders; if (headers != null) Headers = headers; }
public void CompressionSuccessful() { var clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/test.txt"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "http"), }; var clientCompressor = new CompressionProcessor(ConnectionEnd.Client); var serverDecompressor = new CompressionProcessor(ConnectionEnd.Server); var serializedHeaders = clientCompressor.Compress(clientHeaders); var decompressedHeaders = new HeadersList(serverDecompressor.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } var serverHeaders = new HeadersList { new KeyValuePair <string, string>(":status", StatusCode.Code200Ok.ToString()), }; var serverCompressor = new CompressionProcessor(ConnectionEnd.Server); var clientDecompressor = new CompressionProcessor(ConnectionEnd.Client); serializedHeaders = serverCompressor.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressor.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } }
/// <summary> /// Sends the headers with request headers. /// </summary> /// <param name="pairs">The header pairs.</param> /// <param name="priority">The stream priority.</param> /// <param name="isEndStream">True if initial headers+priority is also the final frame from endpoint.</param> public void SendRequest(HeadersList pairs, Priority priority, bool isEndStream) { var stream = CreateStream(priority); stream.WriteHeadersFrame(pairs, isEndStream, true); if (OnRequestSent != null) { OnRequestSent(this, new RequestSentEventArgs(stream)); } }
/// <summary> /// Writes the status header to the stream. /// </summary> /// <param name="final">if set to <c>true</c> then marks headers frame as final.</param> private void SendHeaders(bool final) { var responseHeaders = new HeadersList(_responseHeaders) { new KeyValuePair<string, string>(CommonHeaders.Status, _owinContext.Response.StatusCode.ToString(CultureInfo.InvariantCulture)) }; _protocolStream.WriteHeadersFrame(responseHeaders, final, true); }
//localPath should be provided only for post and put cmds //serverPostAct should be provided only for post cmd private void SubmitRequest(Uri request, string method, string localPath = null, string serverPostAct = null) { var headers = new HeadersList { new KeyValuePair<string, string>(CommonHeaders.Method, method), new KeyValuePair<string, string>(CommonHeaders.Path, request.PathAndQuery), new KeyValuePair<string, string>(CommonHeaders.Version, _version), new KeyValuePair<string, string>(CommonHeaders.Host, _host), new KeyValuePair<string, string>(CommonHeaders.Scheme, _scheme), }; //Put and post handling /*if (!String.IsNullOrEmpty(localPath)) { headers.Add(new KeyValuePair<string, string>(":localPath".ToLower(), localPath)); } if (!String.IsNullOrEmpty(serverPostAct)) { headers.Add(new KeyValuePair<string, string>(":serverPostAct".ToLower(), serverPostAct)); }*/ //Sending request with default priority _sessionAdapter.SendRequest(headers, Constants.DefaultStreamPriority, false); }
//localPath should be provided only for post and put cmds //serverPostAct should be provided only for post cmd private void SubmitRequest(Uri request, string method, string localPath = null, string serverPostAct = null) { var headers = new HeadersList { new KeyValuePair<string, string>(":method", method), new KeyValuePair<string, string>(":path", request.PathAndQuery), new KeyValuePair<string, string>(":version", _version), new KeyValuePair<string, string>(":host", _host), new KeyValuePair<string, string>(":scheme", _scheme), }; if (!String.IsNullOrEmpty(localPath)) { headers.Add(new KeyValuePair<string, string>(":localPath".ToLower(), localPath)); } if (!String.IsNullOrEmpty(serverPostAct)) { headers.Add(new KeyValuePair<string, string>(":serverPostAct".ToLower(), serverPostAct)); } //Sending request with average priority _clientSession.SendRequest(headers, Priority.None, false); }
/// <summary> /// Write the response status and compressed header. /// </summary> /// <param name="writeEndOfHeaders">Write the end of the header bytes, carrige return line feed.</param> /// <param name="writeResponseStatus">Write the response status (:status = 200).</param> /// <param name="compressed">Compress the headers.</param> /// <param name="headerFrame">Only header frame types are supported.</param> public void WriteResponseHeaders(bool writeEndOfHeaders = true, bool writeResponseStatus = true, bool compressed = true, Protocol.OpCodeFrame headerFrame = Protocol.OpCodeFrame.Headers) { byte[] buffer = null; string data = ""; HeadersList headers = new HeadersList(); // If chunked is used. if (SendChunked) { AddHeader("Transfer-Encoding", "Chunked"); } // If the server has been specified. if (!String.IsNullOrEmpty(Server)) { AddHeader("Server", Server); } // If content length has been specified. if (ContentLength > 0) { AddHeader("Content-Length", ContentLength.ToString()); } // If the allow has been specified. if (!String.IsNullOrEmpty(Allow)) { AddHeader("Allow", Allow); } // If the content type has been specified. if (!String.IsNullOrEmpty(ContentType)) { AddHeader("Content-Type", ContentType); } // If the Upgrade has been specified. if (!String.IsNullOrEmpty(Upgrade)) { // If an upgrade is required // then set the connection to upgrade // and set the upgrade to the protocol (e.g. WebSocket, HTTP/2.0 .. etc). AddHeader("Connection", "Upgrade"); AddHeader("Upgrade", Upgrade); } else { // If the connection is open. if (KeepAlive) { AddHeader("Connection", "Keep-Alive"); } } // If the content encoding has been specified. if (!String.IsNullOrEmpty(ContentEncoding)) { AddHeader("Content-Encoding", ContentEncoding); } // If the content lanaguage has been specified. if (!String.IsNullOrEmpty(ContentLanguage)) { AddHeader("Content-Language", ContentLanguage); } // If authenticate type is other than none. if (AuthorizationType != Nequeo.Security.AuthenticationType.None) { AddHeader("WWW-Authenticate", AuthorizationType.ToString()); } // Write response status. if (writeResponseStatus) { // Compress the headers if (compressed) { // Set the response status. headers.Add(new KeyValuePair <string, string>(":status", StatusCode.ToString() + (StatusSubcode > 0 ? "." + StatusSubcode.ToString() : ""))); } else { // Send the http response status. data = ":status = " + StatusCode.ToString() + (StatusSubcode > 0 ? "." + StatusSubcode.ToString() : "") + _deli; buffer = Encoding.Default.GetBytes(data); Write(buffer, 0, buffer.Length); } } // If headers exists. if (Headers != null) { // For each header found. foreach (string header in Headers.AllKeys) { // Compress the headers if (compressed) { // Add each header. headers.Add(new KeyValuePair <string, string>(header.ToLower(), Headers[header])); } else { // Add each header. data = header.ToLower() + " = " + Headers[header] + _deli; buffer = Encoding.Default.GetBytes(data); Write(buffer, 0, buffer.Length); } } } // If cookies exists. if (Cookies != null) { // For each cookie found. foreach (Cookie cookie in Cookies) { // Make shore the cookie has been set. if (!String.IsNullOrEmpty(cookie.Name) && !String.IsNullOrEmpty(cookie.Value)) { // Compress the headers if (compressed) { // Get the cookie details. headers.Add(new KeyValuePair <string, string>("set-cookie", cookie.Name + "=" + cookie.Value + (cookie.Expires != null ? "; Expires=" + cookie.Expires.ToUniversalTime().ToLongDateString() + " " + cookie.Expires.ToUniversalTime().ToLongTimeString() + " GMT" : "") + (!String.IsNullOrEmpty(cookie.Path) ? "; Path=" + cookie.Path : "") + (!String.IsNullOrEmpty(cookie.Domain) ? "; Domain=" + cookie.Domain : "") + (cookie.Version > 0 ? "; Version=" + cookie.Version : "") + (cookie.Secure ? "; Secure" : "") + (cookie.HttpOnly ? "; HttpOnly" : "") )); } else { // Get the cookie details. data = "Set-Cookie" + ": " + cookie.Name + "=" + cookie.Value + (cookie.Expires != null ? "; Expires=" + cookie.Expires.ToUniversalTime().ToLongDateString() + " " + cookie.Expires.ToUniversalTime().ToLongTimeString() + " GMT" : "") + (!String.IsNullOrEmpty(cookie.Path) ? "; Path=" + cookie.Path : "") + (!String.IsNullOrEmpty(cookie.Domain) ? "; Domain=" + cookie.Domain : "") + (cookie.Version > 0 ? "; Version=" + cookie.Version : "") + (cookie.Secure ? "; Secure" : "") + (cookie.HttpOnly ? "; HttpOnly" : "") + _deli; // Write to the stream. buffer = Encoding.Default.GetBytes(data); Write(buffer, 0, buffer.Length); } } } } // Compress the headers if (compressed) { // Compress the headers. buffer = Utility.CompressHeaders(headers); Write(buffer, 0, buffer.Length); } else { // Write the end of the headers. if (writeEndOfHeaders) { // Send the header end space. data = _deli; buffer = Encoding.Default.GetBytes(data); Write(buffer, 0, buffer.Length); } } }
public SizedHeadersList(HeadersList headers) : base(headers) { }
public Http2Session(SecureSocket sessionSocket, ConnectionEnd end, bool usePriorities, bool useFlowControl, IDictionary<string, object> handshakeResult = null) { _ourEnd = end; _usePriorities = usePriorities; _useFlowControl = useFlowControl; _handshakeHeaders = new Dictionary<string, string>(16); ApplyHandshakeResults(handshakeResult); if (_ourEnd == ConnectionEnd.Client) { _remoteEnd = ConnectionEnd.Server; _lastId = -1; // Streams opened by client are odd } else { _remoteEnd = ConnectionEnd.Client; _lastId = 0; // Streams opened by server are even } _goAwayReceived = false; _settingsManager = new SettingsManager(); _comprProc = new CompressionProcessor(_ourEnd); _sessionSocket = sessionSocket; _frameReader = new FrameReader(_sessionSocket); ActiveStreams = new ActiveStreams(); _writeQueue = new WriteQueue(_sessionSocket, ActiveStreams, _usePriorities); if (_sessionSocket != null && sessionSocket.SecureProtocol == SecureProtocol.None) { OurMaxConcurrentStreams = int.Parse(_handshakeHeaders[":max_concurrent_streams"]); RemoteMaxConcurrentStreams = int.Parse(_handshakeHeaders[":max_concurrent_streams"]); InitialWindowSize = int.Parse(_handshakeHeaders[":initial_window_size"]); } else { OurMaxConcurrentStreams = 100; //Spec recommends value 100 by default RemoteMaxConcurrentStreams = 100; InitialWindowSize = 2000000; } _flowControlManager = new FlowControlManager(this); if (!_useFlowControl) { _flowControlManager.Options = (byte) FlowControlOptions.DontUseFlowControl; } SessionWindowSize = 0; _toBeContinuedHeaders = new HeadersList(); }
public void WriteHeadersFrame(HeadersList headers, bool isEndStream, bool isEndHeaders) { if (headers == null) throw new ArgumentNullException("headers is null"); if (Closed) return; var frame = new HeadersFrame(_id, true) { IsEndHeaders = isEndHeaders, IsEndStream = isEndStream, Headers = headers, }; _writeQueue.WriteFrame(frame); if (frame.IsEndStream) { HalfClosedLocal = true; } else if (ReservedLocal) { HalfClosedRemote = true; } if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(frame)); } }
public void HeadersCompression() { var clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/Y3A9NTcuNjE2NjY1fjM5Ljg2NjY2NSZsdmw9NyZzdHk9ciZxPVlhcm9zbGF2bA=="), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; var clientCompressionProc = new CompressionProcessor(); var serverCompressionProc = new CompressionProcessor(); var serializedHeaders = clientCompressionProc.Compress(clientHeaders); var decompressedHeaders = new HeadersList(serverCompressionProc.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } var serverHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/simpleTest.txt"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } serverHeaders = new HeadersList { new KeyValuePair <string, string>(":status", StatusCode.Code404NotFound.ToString(CultureInfo.InvariantCulture)), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } serverHeaders = new HeadersList { new KeyValuePair <string, string>("content-type", "text/plain"), new KeyValuePair <string, string>("last-modified", "Wed, 23 Oct 2013 21:32:06 GMT"), new KeyValuePair <string, string>("etag", "1cedo15cb041fc1"), new KeyValuePair <string, string>("content-length", "749761"), new KeyValuePair <string, string>(":status", StatusCode.Code200Ok.ToString(CultureInfo.InvariantCulture)), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/index.html"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; serializedHeaders = clientCompressionProc.Compress(clientHeaders); decompressedHeaders = new HeadersList(serverCompressionProc.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } }
/// <summary> /// Sends the headers with request headers. /// </summary> /// <param name="pairs">The header pairs.</param> /// <param name="priority">The stream priority.</param> /// <param name="isEndStream">True if initial headers+priority is also the final frame from endpoint.</param> public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_ourEnd == ConnectionEnd.Server) throw new ProtocolError(ResetStatusCode.ProtocolError, "Server should not initiate request"); if (pairs == null) throw new ArgumentNullException("pairs is null"); if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); var path = pairs.GetValue(CommonHeaders.Path); if (path == null) throw new ProtocolError(ResetStatusCode.ProtocolError, "Invalid request ex"); //09 -> 8.2.2. Push Responses //Once a client receives a PUSH_PROMISE frame and chooses to accept the //pushed resource, the client SHOULD NOT issue any requests for the //promised resource until after the promised stream has closed. if (_promisedResources.ContainsValue(path)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Resource has been promised. Client should not request it."); var stream = CreateStream(priority); stream.WriteHeadersFrame(pairs, isEndStream, true); var streamSequence = _headersSequences.Find(stream.Id); streamSequence.AddHeaders(new HeadersFrame(stream.Id, stream.Priority) { Headers = pairs }); if (OnRequestSent != null) { OnRequestSent(this, new RequestSentEventArgs(stream)); } }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <param name="priority"></param> /// <returns></returns> public Http2Stream CreateStream(HeadersList headers, int streamId, int priority = -1) { if (headers == null) throw new ArgumentNullException("pairs is null"); if (priority == -1) priority = Constants.DefaultStreamPriority; if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var stream = new Http2Stream(headers, streamId, _writeQueue, _flowControlManager, priority); var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, priority){Headers = headers})); _headersSequences.Add(streamSequence); ActiveStreams[stream.Id] = stream; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return stream; }
private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream) { Http2Logger.LogDebug("New headers with id = " + headersFrame.StreamId); //spec 06: //If a HEADERS frame //is received whose stream identifier field is 0x0, the recipient MUST //respond with a connection error (Section 5.4.1) of type //PROTOCOL_ERROR [PROTOCOL_ERROR]. if (headersFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with id = 0"); } var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", headersFrame.StreamId, header.Key, header.Value); } headersFrame.Headers.AddRange(headers); var sequence = _headersSequences.Find(seq => seq.StreamId == headersFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(headersFrame.StreamId, headersFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(headersFrame); } if (headersFrame.HasPriority) { sequence.Priority = headersFrame.Priority; } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(headersFrame.StreamId); if (stream == null) { stream = CreateStream(sequence.Headers, headersFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
/// <summary> /// Process a continuation frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="continuationFrame">The continuation frame.</param> /// <param name="stream">The selected stream context.</param> /// <param name="canPassContext">Can the data be sent to the client context.</param> private static void ProcessContinuationFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, ContinuationFrame continuationFrame, out ContextStream stream, out bool canPassContext) { // The data can be sent to the client // through the http context session. canPassContext = false; /* CONTINUATION frames MUST be associated with a stream. If a CONTINUATION * frame is received whose stream identifier field is 0x0, the recipient MUST * respond with a connection error of type PROTOCOL_ERROR. */ if (continuationFrame.StreamId == 0) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Incoming continuation frame with stream id is equal to 0."); } // Attempt to get the sepcific stream. stream = httpContext.GetStream(continuationFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } // Get the number of compressed headers. var serializedHeaders = new byte[continuationFrame.CompressedHeaders.Count]; // Copy the compressed frame. Buffer.BlockCopy(continuationFrame.CompressedHeaders.Array, continuationFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); // Decompress the compressed headers. HeadersList decompressedHeaders = new HeaderCompression().Decompress(serializedHeaders); HeadersList headers = new HeadersList(decompressedHeaders); // Add the list of headers. foreach (KeyValuePair <string, string> header in headers) { stream.HttpRequest.OriginalHeaders.Add(new NameValue() { Name = header.Key, Value = header.Value }); } // Determine if all headers have been found. if (continuationFrame.IsEndHeaders) { // The end of the headers has been found. stream.HttpRequest.HeadersFound = true; } bool wasValidated = false; // Check the current stream state. if (stream.Idle || stream.ReservedRemote) { // Validate all the headers. httpContext.ValidateHeaders(stream); wasValidated = true; } else if (stream.Opened || stream.HalfClosedLocal) { // Validate all the headers. httpContext.ValidateHeaders(stream); wasValidated = true; } else if (stream.HalfClosedRemote) { // Half closed remote stream. throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Continuation for half closed remote stream."); } else { // Stream has no state error. throw new StreamNotFoundException(continuationFrame.StreamId); } // If the headers where validated. if (wasValidated) { // If headers have been found. if (stream.HttpRequest.HeadersFound) { // Set the current stream id httpContext.StreamId = stream.StreamId; stream.HttpRequest.IsEndOfData = continuationFrame.IsEndStream; // Get the request resources. RequestResource resource = Nequeo.Net.Http2.Utility.GetRequestResource(stream.HttpRequest.OriginalHeaders); // Assign the http request content. stream.HttpRequest.ReadRequestHeaders(stream.HttpRequest.OriginalHeaders, resource); // If this is the last bit of data // that has been sent then sent // the data to the client context. if (continuationFrame.IsEndStream) { canPassContext = true; } } } }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <returns></returns> private Http2Stream CreateStream(HeadersList headers, int streamId) { if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { //Remote side tries to open more streams than allowed Dispose(); throw new InvalidOperationException("Trying to create more streams than allowed!"); } var stream = new Http2Stream(headers, streamId, _writeQueue, _flowControlManager, _comprProc); ActiveStreams[stream.Id] = stream; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } }; return stream; }
//TODO Think about: writing push_promise is available in any time now. Need to handle it. public void WritePushPromise(IDictionary<string, string[]> pairs, Int32 promisedId) { if (Id % 2 != 0 && promisedId % 2 != 0) throw new InvalidOperationException("Client cant send push_promise frames"); if (Closed) return; var headers = new HeadersList(pairs); var frame = new PushPromiseFrame(Id, promisedId, true, true, headers); ReservedLocal = true; _writeQueue.WriteFrame(frame); if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(frame)); } }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; //Spec 03 tells that frame with continues flag MUST be followed by a frame with the same type //and the same stread id. if (_toBeContinuedHeaders.Count != 0) { if (_toBeContinuedFrame.FrameType != frame.FrameType || _toBeContinuedFrame.StreamId != frame.StreamId) { //If not, we must close the session. Dispose(); return; } } try { switch (frame.FrameType) { case FrameType.Headers: Http2Logger.LogDebug("New headers with id = " + frame.StreamId); var headersFrame = (HeadersFrame)frame; var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); if (!headersFrame.IsEndHeaders) { _toBeContinuedHeaders.AddRange(decompressedHeaders); _toBeContinuedFrame = headersFrame; break; } headers.AddRange(_toBeContinuedHeaders); _toBeContinuedHeaders.Clear(); _toBeContinuedFrame = null; headersFrame.Headers.AddRange(headers); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); } stream = GetStream(headersFrame.StreamId); if (stream == null) { if (_ourEnd == ConnectionEnd.Server) { string path = headers.GetValue(":path"); if (path == null) { path = _handshakeHeaders.ContainsKey(":path") ? _handshakeHeaders[":path"] : @"\index.html"; headers.Add(new KeyValuePair<string, string>(":path", path)); } } else { headers.AddRange(_handshakeHeaders); } stream = CreateStream(headers, frame.StreamId); } break; case FrameType.Priority: var priorityFrame = (PriorityFrame)frame; Http2Logger.LogDebug("Priority frame. StreamId: {0} Priority: {1}", priorityFrame.StreamId, priorityFrame.Priority); stream = GetStream(priorityFrame.StreamId); if (_usePriorities) { stream.Priority = priorityFrame.Priority; } break; case FrameType.RstStream: var resetFrame = (RstStreamFrame)frame; stream = GetStream(resetFrame.StreamId); if (stream != null) { Http2Logger.LogDebug("RST frame with code " + resetFrame.StatusCode); stream.Dispose(); } break; case FrameType.Data: var dataFrame = (DataFrame)frame; Http2Logger.LogDebug("Data frame. StreamId:{0} Length:{1}", dataFrame.StreamId, dataFrame.FrameLength); stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream != null && stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(InitialWindowSize); } break; case FrameType.Ping: var pingFrame = (PingFrame)frame; Http2Logger.LogDebug("Ping frame with StreamId:{0} Payload:{1}", pingFrame.StreamId, pingFrame.Payload.Count); if (pingFrame.FrameLength != PingFrame.PayloadLength) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Ping payload size is not equal to 8"); } if (pingFrame.IsPong) { _wasPingReceived = true; _pingReceived.Set(); } else { var pongFrame = new PingFrame(true, pingFrame.Payload.ToArray()); _writeQueue.WriteFrame(pongFrame); } break; case FrameType.Settings: //Not first frame in the session. //Client initiates connection and sends settings before request. //It means that if server sent settings before it will not be a first frame, //because client initiates connection. if (_ourEnd == ConnectionEnd.Server && !_wasSettingsReceived && (ActiveStreams.Count > 0)) { Dispose(); return; } var settingFrame = (SettingsFrame)frame; Http2Logger.LogDebug("Settings frame. Entry count: {0} StreamId: {1}", settingFrame.EntryCount, settingFrame.StreamId); _wasSettingsReceived = true; _settingsManager.ProcessSettings(settingFrame, this, _flowControlManager); if (_ourEnd == ConnectionEnd.Server && _sessionSocket.SecureProtocol == SecureProtocol.None) { //The HTTP/1.1 request that is sent prior to upgrade is associated with //stream 1 and is assigned the highest possible priority. Stream 1 is //implicitly half closed from the client toward the server, since the //request is completed as an HTTP/1.1 request. After commencing the //HTTP/2.0 connection, stream 1 is used for the response. stream = CreateStream(Priority.Pri0); stream.EndStreamReceived = true; stream.Headers.Add(new KeyValuePair<string, string>(":method", _handshakeHeaders[":method"])); stream.Headers.Add(new KeyValuePair<string, string>(":path", _handshakeHeaders[":path"])); OnFrameReceived(this, new FrameReceivedEventArgs(stream, new HeadersFrame(stream.Id, false))); } break; case FrameType.WindowUpdate: if (_useFlowControl) { var windowFrame = (WindowUpdateFrame)frame; Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowFrame.Delta, windowFrame.StreamId); stream = GetStream(windowFrame.StreamId); if (stream != null) { stream.UpdateWindowSize(windowFrame.Delta); stream.PumpUnshippedFrames(); } } break; case FrameType.GoAway: _goAwayReceived = true; Http2Logger.LogDebug("GoAway frame received"); Dispose(); break; default: throw new NotImplementedException(frame.FrameType.ToString()); } if (stream != null && frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Http2Logger.LogDebug("Final frame received for stream with id = " + stream.Id); stream.EndStreamReceived = true; } if (stream != null && OnFrameReceived != null) { OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); } } //Frame came for already closed stream. Ignore it. //Spec: //An endpoint that sends RST_STREAM MUST ignore //frames that it receives on closed streams if it sends RST_STREAM. // //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM or a frame //containing a END_STREAM flag on that stream MUST treat that as a //stream error (Section 5.4.2) of type PROTOCOL_ERROR. catch (Http2StreamNotFoundException) { if (stream != null) { stream.WriteRst(ResetStatusCode.ProtocolError); } else { //GoAway? } } catch (CompressionError ex) { //The endpoint is unable to maintain the compression context for the connection. Http2Logger.LogError("Compression error occurred: " + ex.Message); Close(ResetStatusCode.CompressionError); } catch (ProtocolError pEx) { Http2Logger.LogError("Protocol error occurred: " + pEx.Message); Close(pEx.Code); } }
//Incoming internal Http2Stream(HeadersList headers, int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority) : this(id, writeQueue, flowCrtlManager, priority) { Headers = headers; }
/// <summary> /// Modifies the table. /// </summary> /// <param name="headerName">Name of the header.</param> /// <param name="headerValue">The header value.</param> /// <param name="headerType">Type of the header.</param> /// <param name="useHeadersTable">The use headers table.</param> /// <param name="index">The index.</param> private void ModifyTable(string headerName, string headerValue, IndexationType headerType, HeadersList useHeadersTable, int index) { int headerLen = headerName.Length + headerValue.Length + sizeof(Int32); //spec 04: 3.2.3. Header Table Management // The header table can be modified by either adding a new entry to it //or by replacing an existing one. Before doing such a modification, //it has to be ensured that the header table size will stay lower than //or equal to the SETTINGS_MAX_BUFFER_SIZE limit (see Section 5). To //achieve this, repeatedly, the first entry of the header table is //removed, until enough space is available for the modification. //A consequence of removing one or more entries at the beginning of the //header table is that the remaining entries are renumbered. The first //entry of the header table is always associated to the index 0. //When the modification of the header table is the replacement of an //existing entry, the replaced entry is the one indicated in the //literal representation before any entry is removed from the header //table. If the entry to be replaced is removed from the header table //when performing the size adjustment, the replacement entry is //inserted at the beginning of the header table. //The addition of a new entry with a size greater than the //SETTINGS_MAX_BUFFER_SIZE limit causes all the entries from the header //table to be dropped and the new entry not to be added to the header //table. The replacement of an existing entry with a new entry with a //size greater than the SETTINGS_MAX_BUFFER_SIZE has the same //consequences. switch (headerType) { case IndexationType.Incremental: while (useHeadersTable.StoredHeadersSize + headerLen > MaxHeaderByteSize && useHeadersTable.Count > 0) { useHeadersTable.RemoveAt(0); } useHeadersTable.Add(new KeyValuePair<string, string>(headerName, headerValue)); break; case IndexationType.Substitution: if (index != -1) { var header = useHeadersTable[index]; int substHeaderLen = header.Key.Length + header.Value.Length + sizeof(Int32); int deletedHeadersCounter = 0; while (useHeadersTable.StoredHeadersSize + headerLen - substHeaderLen > MaxHeaderByteSize) { if (useHeadersTable.Count > 0) { useHeadersTable.RemoveAt(0); deletedHeadersCounter++; } } if (index >= deletedHeadersCounter) { useHeadersTable[index - deletedHeadersCounter] = new KeyValuePair<string, string>(headerName, headerValue); } else { useHeadersTable.Insert(0, new KeyValuePair<string, string>(headerName, headerValue)); } } //If header wasn't found then add it to the table else { while (useHeadersTable.StoredHeadersSize + headerLen > MaxHeaderByteSize && useHeadersTable.Count > 0) { useHeadersTable.RemoveAt(0); } useHeadersTable.Add(new KeyValuePair<string, string>(headerName, headerValue)); } break; default: return; } }
/// <summary> /// Overrides request processing logic. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame">The request header frame.</param> /// <returns></returns> protected override void ProcessRequest(Http2Stream stream, Frame frame) { /* 12 -> 8.1.3.1 * All HTTP/2 requests MUST include exactly one valid value for the * ":method", ":scheme", and ":path" header fields, unless this is a * CONNECT request (Section 8.3). An HTTP request that omits mandatory * header fields is malformed (Section 8.1.3.5). */ if (stream.Headers.GetValue(CommonHeaders.Method) == null || stream.Headers.GetValue(CommonHeaders.Path) == null || stream.Headers.GetValue(CommonHeaders.Scheme) == null) { stream.WriteRst(ResetStatusCode.ProtocolError); stream.Close(ResetStatusCode.ProtocolError); return; } Task.Factory.StartNew(async() => { try { var context = new Http2OwinMessageContext(stream); var contextEnv = context.OwinContext.Environment; PushFunc pushDelegate = null; pushDelegate = async pairs => { var promisedStream = CreateStream(); //assume that we have already received endStream promisedStream.HalfClosedLocal = true; stream.WritePushPromise(pairs, promisedStream.Id); var headers = new HeadersList(pairs); promisedStream.Headers.AddRange(headers); var http2MsgCtx = new Http2OwinMessageContext(promisedStream); var http2PushCtx = http2MsgCtx.OwinContext; http2PushCtx.Set(CommonOwinKeys.ServerPushFunc, pushDelegate); //pass add info from parent to child context. This info can store //reference table for example or something els that should be passed from //client request into child push requests. if (contextEnv.ContainsKey(CommonOwinKeys.AdditionalInfo)) { http2PushCtx.Set(CommonOwinKeys.AdditionalInfo, contextEnv[CommonOwinKeys.AdditionalInfo]); } await _next(http2PushCtx); http2MsgCtx.FinishResponse(); }; context.OwinContext.Set(CommonOwinKeys.ServerPushFunc, pushDelegate); context.OwinContext.Set(CommonOwinKeys.EnableServerPush, _isPushEnabled); await _next(context.OwinContext); context.FinishResponse(); } catch (Exception ex) { EndResponse(stream, ex); } }); }