Make Frame implements IHttpConnectionFeature

Provide RemoteIPAddress as well as RemotePort
This commit is contained in:
Troy Dai 2015-10-27 14:40:28 -07:00
parent cb623e55f1
commit e9a6061023
10 changed files with 297 additions and 42 deletions

View File

@ -52,6 +52,11 @@ namespace SampleApp
context.Request.Path,
context.Request.QueryString);
var connectionFeature = context.Connection;
Console.WriteLine($"Peer: {connectionFeature.RemoteIpAddress?.ToString()} {connectionFeature.RemotePort}");
Console.WriteLine($"Sock: {connectionFeature.LocalIpAddress?.ToString()} {connectionFeature.LocalPort}");
Console.WriteLine($"IsLocal: {connectionFeature.IsLocal}");
context.Response.ContentLength = 11;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Hello world");

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
@ -28,6 +29,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private readonly object _stateLock = new object();
private ConnectionState _connectionState;
private IPEndPoint _remoteEndPoint;
private IPEndPoint _localEndPoint;
public Connection(ListenerContext context, UvStreamHandle socket) : base(context)
{
_socket = socket;
@ -46,13 +50,20 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
// Start socket prior to applying the ConnectionFilter
_socket.ReadStart(_allocCallback, _readCallback, this);
var tcpHandle = _socket as UvTcpHandle;
if (tcpHandle != null)
{
_remoteEndPoint = tcpHandle.GetPeerIPEndPoint();
_localEndPoint = tcpHandle.GetSockIPEndPoint();
}
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
if (ConnectionFilter == null)
{
SocketInput = _rawSocketInput;
SocketOutput = _rawSocketOutput;
_frame = new Frame(this);
_frame = CreateFrame();
_frame.Start();
}
else
@ -94,7 +105,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
SocketInput = filteredStreamAdapter.SocketInput;
SocketOutput = filteredStreamAdapter.SocketOutput;
_frame = new Frame(this);
_frame = CreateFrame();
_frame.Start();
}
@ -142,6 +153,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_rawSocketInput.IncomingComplete(readCount, error);
}
private Frame CreateFrame()
{
return new Frame(this, _remoteEndPoint, _localEndPoint);
}
void IConnectionControl.Pause()
{
Log.ConnectionPause(_connectionId);

View File

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
@ -13,7 +14,11 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
public partial class Frame : IFeatureCollection, IHttpRequestFeature, IHttpResponseFeature, IHttpUpgradeFeature
public partial class Frame : IFeatureCollection,
IHttpRequestFeature,
IHttpResponseFeature,
IHttpUpgradeFeature,
IHttpConnectionFeature
{
// NOTE: When feature interfaces are added to or removed from this Frame class implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
@ -246,6 +251,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
int IFeatureCollection.Revision => _featureRevision;
IPAddress IHttpConnectionFeature.RemoteIpAddress { get; set; }
IPAddress IHttpConnectionFeature.LocalIpAddress { get; set; }
int IHttpConnectionFeature.RemotePort { get; set; }
int IHttpConnectionFeature.LocalPort { get; set; }
bool IHttpConnectionFeature.IsLocal { get; set; }
object IFeatureCollection.this[Type key]
{
get { return FastFeatureGet(key); }

View File

@ -45,11 +45,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_currentIHttpRequestFeature = this;
_currentIHttpResponseFeature = this;
_currentIHttpUpgradeFeature = this;
_currentIHttpConnectionFeature = this;
_currentIHttpRequestIdentifierFeature = null;
_currentIServiceProvidersFeature = null;
_currentIHttpRequestLifetimeFeature = null;
_currentIHttpConnectionFeature = null;
_currentIHttpAuthenticationFeature = null;
_currentIQueryFeature = null;
_currentIFormFeature = null;

View File

@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
@ -44,8 +46,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private bool _autoChunk;
private Exception _applicationException;
public Frame(ConnectionContext context) : base(context)
private readonly IPEndPoint _localEndPoint;
private readonly IPEndPoint _remoteEndPoint;
public Frame(ConnectionContext context)
: this(context, remoteEndPoint: null, localEndPoint: null)
{
}
public Frame(ConnectionContext context,
IPEndPoint remoteEndPoint,
IPEndPoint localEndPoint)
: base(context)
{
_remoteEndPoint = remoteEndPoint;
_localEndPoint = localEndPoint;
FrameControl = this;
Reset();
}
@ -99,6 +115,21 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
ResponseBody = null;
DuplexStream = null;
var httpConnectionFeature = this as IHttpConnectionFeature;
httpConnectionFeature.RemoteIpAddress = _remoteEndPoint?.Address;
httpConnectionFeature.RemotePort = _remoteEndPoint?.Port ?? 0;
httpConnectionFeature.LocalIpAddress = _localEndPoint?.Address;
httpConnectionFeature.LocalPort = _localEndPoint?.Port ?? 0;
if (_remoteEndPoint != null && _localEndPoint != null)
{
httpConnectionFeature.IsLocal = _remoteEndPoint.Address.Equals(_localEndPoint.Address);
}
else
{
httpConnectionFeature.IsLocal = false;
}
}
public void ResetResponseHeaders()
@ -123,7 +154,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Should be called when the server wants to initiate a shutdown. The Task returned will
/// become complete when the RequestProcessingAsync function has exited. It is expected that
/// Stop will be called on all active connections, and Task.WaitAll() will be called on every
/// Stop will be called on all active connections, and Task.WaitAll() will be called on every
/// return value.
/// </summary>
public Task Stop()
@ -136,7 +167,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
/// <summary>
/// Primary loop which consumes socket input, parses it for protocol framing, and invokes the
/// Primary loop which consumes socket input, parses it for protocol framing, and invokes the
/// application delegate for as long as the socket is intended to remain open.
/// The resulting Task from this loop is preserved in a field which is used when the server needs
/// to drain and close all currently active connections.
@ -731,7 +762,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if (chSecond != '\n')
{
// "\r" was all by itself, move just after it and try again
// "\r" was all by itself, move just after it and try again
scan = endValue;
scan.Take();
continue;
@ -740,7 +771,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
var chThird = scan.Peek();
if (chThird == ' ' || chThird == '\t')
{
// special case, "\r\n " or "\r\n\t".
// special case, "\r\n " or "\r\n\t".
// this is considered wrapping"linear whitespace" and is actually part of the header value
// continue past this for the next
wrapping = true;

View File

@ -56,6 +56,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_uv_req_size = NativeDarwinMonoMethods.uv_req_size;
_uv_ip4_addr = NativeDarwinMonoMethods.uv_ip4_addr;
_uv_ip6_addr = NativeDarwinMonoMethods.uv_ip6_addr;
_uv_tcp_getpeername = NativeDarwinMonoMethods.uv_tcp_getpeername;
_uv_tcp_getsockname = NativeDarwinMonoMethods.uv_tcp_getsockname;
_uv_walk = NativeDarwinMonoMethods.uv_walk;
}
else
@ -95,6 +97,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_uv_req_size = NativeMethods.uv_req_size;
_uv_ip4_addr = NativeMethods.uv_ip4_addr;
_uv_ip6_addr = NativeMethods.uv_ip6_addr;
_uv_tcp_getpeername = NativeMethods.uv_tcp_getpeername;
_uv_tcp_getsockname = NativeMethods.uv_tcp_getsockname;
_uv_walk = NativeMethods.uv_walk;
}
}
@ -206,9 +210,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
Check(_uv_tcp_init(loop, handle));
}
protected delegate int uv_tcp_bind_func(UvTcpHandle handle, ref sockaddr addr, int flags);
protected delegate int uv_tcp_bind_func(UvTcpHandle handle, ref SockAddr addr, int flags);
protected uv_tcp_bind_func _uv_tcp_bind;
public void tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags)
public void tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags)
{
handle.Validate();
Check(_uv_tcp_bind(handle, ref addr, flags));
@ -365,16 +369,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
return _uv_req_size(reqType);
}
protected delegate int uv_ip4_addr_func(string ip, int port, out sockaddr addr);
protected delegate int uv_ip4_addr_func(string ip, int port, out SockAddr addr);
protected uv_ip4_addr_func _uv_ip4_addr;
public int ip4_addr(string ip, int port, out sockaddr addr, out Exception error)
public int ip4_addr(string ip, int port, out SockAddr addr, out Exception error)
{
return Check(_uv_ip4_addr(ip, port, out addr), out error);
}
protected delegate int uv_ip6_addr_func(string ip, int port, out sockaddr addr);
protected delegate int uv_ip6_addr_func(string ip, int port, out SockAddr addr);
protected uv_ip6_addr_func _uv_ip6_addr;
public int ip6_addr(string ip, int port, out sockaddr addr, out Exception error)
public int ip6_addr(string ip, int port, out SockAddr addr, out Exception error)
{
return Check(_uv_ip6_addr(ip, port, out addr), out error);
}
@ -388,26 +392,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_uv_walk(loop, walk_cb, arg);
}
public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
protected uv_tcp_getsockname_func _uv_tcp_getsockname;
public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
{
handle.Validate();
Check(_uv_tcp_getsockname(handle, out addr, ref namelen));
}
public delegate int uv_tcp_getpeername_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
protected uv_tcp_getpeername_func _uv_tcp_getpeername;
public void tcp_getpeername(UvTcpHandle handle, out SockAddr addr, ref int namelen)
{
handle.Validate();
Check(_uv_tcp_getpeername(handle, out addr, ref namelen));
}
public uv_buf_t buf_init(IntPtr memory, int len)
{
return new uv_buf_t(memory, len, IsWindows);
}
public struct sockaddr
{
// this type represents native memory occupied by sockaddr struct
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
// although the c/c++ header defines it as a 2-byte short followed by a 14-byte array,
// the simplest way to reserve the same size in c# is with four nameless long values
private long _field0;
private long _field1;
private long _field2;
private long _field3;
public sockaddr(long ignored) { _field3 = _field0 = _field1 = _field2 = _field3 = 0; }
}
public struct uv_buf_t
{
// this type represents a WSABUF struct on Windows
@ -504,7 +509,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public static extern int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags);
public static extern int uv_tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_open(UvTcpHandle handle, IntPtr hSocket);
@ -564,10 +569,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public static extern int uv_req_size(RequestType reqType);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_ip4_addr(string ip, int port, out sockaddr addr);
public static extern int uv_ip4_addr(string ip, int port, out SockAddr addr);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_ip6_addr(string ip, int port, out sockaddr addr);
public static extern int uv_ip6_addr(string ip, int port, out SockAddr addr);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_getsockname(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_getpeername(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);
@ -606,7 +617,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public static extern int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags);
public static extern int uv_tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_open(UvTcpHandle handle, IntPtr hSocket);
@ -666,10 +677,16 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public static extern int uv_req_size(RequestType reqType);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_ip4_addr(string ip, int port, out sockaddr addr);
public static extern int uv_ip4_addr(string ip, int port, out SockAddr addr);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_ip6_addr(string ip, int port, out sockaddr addr);
public static extern int uv_ip6_addr(string ip, int port, out SockAddr addr);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_getsockname(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_tcp_getpeername(UvTcpHandle handle, out SockAddr name, ref int namelen);
[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);

View File

@ -0,0 +1,94 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
namespace Microsoft.AspNet.Server.Kestrel.Networking
{
public struct SockAddr
{
// this type represents native memory occupied by sockaddr struct
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
// although the c/c++ header defines it as a 2-byte short followed by a 14-byte array,
// the simplest way to reserve the same size in c# is with four nameless long values
private long _field0;
private long _field1;
private long _field2;
private long _field3;
public SockAddr(long ignored)
{
_field3 = _field0 = _field1 = _field2 = _field3 = 0;
}
public IPEndPoint GetIPEndPoint()
{
// The bytes are represented in network byte order.
//
// Example 1: [2001:4898:e0:391:b9ef:1124:9d3e:a354]:39179
//
// 0000 0000 0b99 0017 => The third and fourth bytes 990B is the actual port
// 9103 e000 9848 0120 => IPv6 address is represented in the 128bit field1 and field2.
// 54a3 3e9d 2411 efb9 Read these two 64-bit long from right to left byte by byte.
// 0000 0000 0000 0000
//
// Example 2: 10.135.34.141:39178 when adopt dual-stack sockets, IPv4 is mapped to IPv6
//
// 0000 0000 0a99 0017 => The port representation are the same
// 0000 0000 0000 0000
// 8d22 870a ffff 0000 => IPv4 occupies the last 32 bit: 0A.87.22.8d is the actual address.
// 0000 0000 0000 0000
//
// Example 3: 10.135.34.141:12804, not dual-stack sockets
// 8d22 870a fd31 0002 => sa_family == AF_INET (02)
// 0000 0000 0000 0000
// 0000 0000 0000 0000
// 0000 0000 0000 0000
// Quick calculate the port by mask the field and locate the byte 3 and byte 4
// and then shift them to correct place to form a int.
var port = ((int)(_field0 & 0x00FF0000) >> 8) | (int)((_field0 & 0xFF000000) >> 24);
var family = (int)_field0 & 0x000000FF;
if (family == 0x00000002)
{
// AF_INET => IPv4
return new IPEndPoint(new IPAddress((_field0 >> 32) & 0xFFFFFFFF), port);
}
else if (IsIPv4MappedToIPv6())
{
var ipv4bits = (_field2 >> 32) & 0x00000000FFFFFFFF;
return new IPEndPoint(new IPAddress(ipv4bits), port);
}
else
{
// otherwise IPv6
var bytes1 = BitConverter.GetBytes(_field1);
var bytes2 = BitConverter.GetBytes(_field2);
var bytes = new byte[16];
for (int i = 0; i < 8; ++i)
{
bytes[i] = bytes1[i];
bytes[i + 8] = bytes2[i];
}
return new IPEndPoint(new IPAddress(bytes), port);
}
}
private bool IsIPv4MappedToIPv6()
{
// If the IPAddress is an IPv4 mapped to IPv6, return the IPv4 representation instead.
// For example [::FFFF:127.0.0.1] will be transform to IPAddress of 127.0.0.1
if (_field1 != 0)
{
return false;
}
return (_field2 & 0xFFFFFFFF) == 0xFFFF0000;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Net;
using System.Runtime.InteropServices;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
namespace Microsoft.AspNet.Server.Kestrel.Networking
@ -17,7 +18,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
{
CreateMemory(
loop.Libuv,
loop.ThreadId,
loop.ThreadId,
loop.Libuv.handle_size(Libuv.HandleType.TCP));
_uv.tcp_init(loop, this);
@ -26,7 +27,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public void Init(UvLoopHandle loop, Action<Action<IntPtr>, IntPtr> queueCloseHandle)
{
CreateHandle(
loop.Libuv,
loop.Libuv,
loop.ThreadId,
loop.Libuv.handle_size(Libuv.HandleType.TCP), queueCloseHandle);
@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
{
var endpoint = CreateIPEndpoint(address);
Libuv.sockaddr addr;
SockAddr addr;
var addressText = endpoint.Address.ToString();
Exception error1;
@ -56,6 +57,24 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_uv.tcp_bind(this, ref addr, 0);
}
public IPEndPoint GetPeerIPEndPoint()
{
SockAddr socketAddress;
int namelen = Marshal.SizeOf<SockAddr>();
_uv.tcp_getpeername(this, out socketAddress, ref namelen);
return socketAddress.GetIPEndPoint();
}
public IPEndPoint GetSockIPEndPoint()
{
SockAddr socketAddress;
int namelen = Marshal.SizeOf<SockAddr>();
_uv.tcp_getsockname(this, out socketAddress, ref namelen);
return socketAddress.GetIPEndPoint();
}
public void Open(IntPtr hSocket)
{
_uv.tcp_open(this, hSocket);

View File

@ -9,6 +9,8 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
@ -47,7 +49,7 @@ namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
await context.Response.WriteAsync(total.ToString(CultureInfo.InvariantCulture));
});
});
});
using (var app = hostBuilder.Build().Start())
{
@ -58,7 +60,7 @@ namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
{
bytes[i] = (byte)i;
}
var response = await client.PostAsync("http://localhost:8791/", new ByteArrayContent(bytes));
response.EnsureSuccessStatusCode();
var sizeString = await response.Content.ReadAsStringAsync();
@ -66,5 +68,60 @@ namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
}
}
}
[Theory]
[InlineData("127.0.0.1", "127.0.0.1", "8792")]
[InlineData("localhost", "127.0.0.1", "8792")]
public Task RemoteIPv4Address(string requestAddress, string expectAddress, string port)
{
return TestRemoteIPAddress("localhost", requestAddress, expectAddress, port);
}
[Fact]
public Task RemoteIPv6Address()
{
return TestRemoteIPAddress("[::1]", "[::1]", "::1", "8792");
}
private async Task TestRemoteIPAddress(string registerAddress, string requestAddress, string expectAddress, string port)
{
var config = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string, string> {
{ "server.urls", $"http://{registerAddress}:{port}" }
}).Build();
var builder = new WebHostBuilder(config)
.UseServer("Microsoft.AspNet.Server.Kestrel")
.UseStartup(app =>
{
app.Run(async context =>
{
var connection = context.Connection;
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
RemoteIPAddress = connection.RemoteIpAddress?.ToString(),
RemotePort = connection.RemotePort,
LocalIPAddress = connection.LocalIpAddress?.ToString(),
LocalPort = connection.LocalPort,
IsLocal = connection.IsLocal
}));
});
});
using (var app = builder.Build().Start())
using (var client = new HttpClient())
{
var response = await client.GetAsync($"http://{requestAddress}:{port}/");
response.EnsureSuccessStatusCode();
var connectionFacts = await response.Content.ReadAsStringAsync();
Assert.NotEmpty(connectionFacts);
var facts = JsonConvert.DeserializeObject<JObject>(connectionFacts);
Assert.Equal(expectAddress, facts["RemoteIPAddress"].Value<string>());
Assert.NotEmpty(facts["RemotePort"].Value<string>());
Assert.True(facts["IsLocal"].Value<bool>());
}
}
}
}
}

View File

@ -66,6 +66,7 @@ namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
typeof(IHttpRequestFeature),
typeof(IHttpResponseFeature),
typeof(IHttpUpgradeFeature),
typeof(IHttpConnectionFeature)
};
return $@"