mirror of
https://github.com/Azure/DotNetty.git
synced 2026-10-18 22:30:36 +08:00
316 lines
12 KiB
C#
316 lines
12 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
namespace DotNetty.Codecs.Http.Tests.WebSockets
|
|
{
|
|
using System;
|
|
using System.Text;
|
|
using DotNetty.Buffers;
|
|
using DotNetty.Codecs.Http.WebSockets;
|
|
using DotNetty.Common.Internal;
|
|
using DotNetty.Common.Utilities;
|
|
using DotNetty.Transport.Channels;
|
|
using DotNetty.Transport.Channels.Embedded;
|
|
using Xunit;
|
|
|
|
public abstract class WebSocketClientHandshakerTest
|
|
{
|
|
protected abstract WebSocketClientHandshaker NewHandshaker(Uri uri);
|
|
|
|
protected abstract AsciiString GetOriginHeaderName();
|
|
|
|
[Fact]
|
|
public void HostHeaderWs()
|
|
{
|
|
foreach (string scheme in new[] { "ws://", "http://" })
|
|
{
|
|
foreach (string host in new[] { /*"localhost", "127.0.0.1", "[::1]",*/ "Netty.io" })
|
|
{
|
|
string enter = scheme + host;
|
|
|
|
this.HostHeader(enter, host);
|
|
this.HostHeader(enter + '/', host);
|
|
this.HostHeader(enter + ":80", host);
|
|
this.HostHeader(enter + ":443", host + ":443");
|
|
this.HostHeader(enter + ":9999", host + ":9999");
|
|
this.HostHeader(enter + "/path", host);
|
|
this.HostHeader(enter + ":80/path", host);
|
|
this.HostHeader(enter + ":443/path", host + ":443");
|
|
this.HostHeader(enter + ":9999/path", host + ":9999");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostHeaderWss()
|
|
{
|
|
foreach (string scheme in new[] { "wss://", "https://" })
|
|
{
|
|
foreach (string host in new[] { "localhost", "127.0.0.1", "[::1]", "Netty.io" })
|
|
{
|
|
string enter = scheme + host;
|
|
|
|
this.HostHeader(enter, host);
|
|
this.HostHeader(enter + '/', host);
|
|
this.HostHeader(enter + ":80", host + ":80");
|
|
this.HostHeader(enter + ":443", host);
|
|
this.HostHeader(enter + ":9999", host + ":9999");
|
|
this.HostHeader(enter + "/path", host);
|
|
this.HostHeader(enter + ":80/path", host + ":80");
|
|
this.HostHeader(enter + ":443/path", host);
|
|
this.HostHeader(enter + ":9999/path", host + ":9999");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HostHeaderWithoutScheme()
|
|
{
|
|
this.HostHeader("//localhost/", "localhost");
|
|
this.HostHeader("//localhost/path", "localhost");
|
|
this.HostHeader("//localhost:80/", "localhost:80");
|
|
this.HostHeader("//localhost:443/", "localhost:443");
|
|
this.HostHeader("//localhost:9999/", "localhost:9999");
|
|
}
|
|
|
|
[Fact]
|
|
public void OriginHeaderWs()
|
|
{
|
|
foreach (string scheme in new[] { "ws://", "http://" })
|
|
{
|
|
foreach (string host in new[] { "localhost", "127.0.0.1", "[::1]", "NETTY.IO" })
|
|
{
|
|
string enter = scheme + host;
|
|
string expect = "http://" + host.ToLower();
|
|
|
|
this.OriginHeader(enter, expect);
|
|
this.OriginHeader(enter + '/', expect);
|
|
this.OriginHeader(enter + ":80", expect);
|
|
this.OriginHeader(enter + ":443", expect + ":443");
|
|
this.OriginHeader(enter + ":9999", expect + ":9999");
|
|
this.OriginHeader(enter + "/path%20with%20ws", expect);
|
|
this.OriginHeader(enter + ":80/path%20with%20ws", expect);
|
|
this.OriginHeader(enter + ":443/path%20with%20ws", expect + ":443");
|
|
this.OriginHeader(enter + ":9999/path%20with%20ws", expect + ":9999");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void OriginHeaderWss()
|
|
{
|
|
foreach (string scheme in new[] { "wss://", "https://" })
|
|
{
|
|
foreach (string host in new[] { "localhost", "127.0.0.1", "[::1]", "NETTY.IO" })
|
|
{
|
|
string enter = scheme + host;
|
|
string expect = "https://" + host.ToLower();
|
|
|
|
this.OriginHeader(enter, expect);
|
|
this.OriginHeader(enter + '/', expect);
|
|
this.OriginHeader(enter + ":80", expect + ":80");
|
|
this.OriginHeader(enter + ":443", expect);
|
|
this.OriginHeader(enter + ":9999", expect + ":9999");
|
|
this.OriginHeader(enter + "/path%20with%20ws", expect);
|
|
this.OriginHeader(enter + ":80/path%20with%20ws", expect + ":80");
|
|
this.OriginHeader(enter + ":443/path%20with%20ws", expect);
|
|
this.OriginHeader(enter + ":9999/path%20with%20ws", expect + ":9999");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void OriginHeaderWithoutScheme()
|
|
{
|
|
this.OriginHeader("//localhost/", "http://localhost");
|
|
this.OriginHeader("//localhost/path", "http://localhost");
|
|
|
|
// http scheme by port
|
|
this.OriginHeader("//localhost:80/", "http://localhost");
|
|
this.OriginHeader("//localhost:80/path", "http://localhost");
|
|
|
|
// https scheme by port
|
|
this.OriginHeader("//localhost:443/", "https://localhost");
|
|
this.OriginHeader("//localhost:443/path", "https://localhost");
|
|
|
|
// http scheme for non standard port
|
|
this.OriginHeader("//localhost:9999/", "http://localhost:9999");
|
|
this.OriginHeader("//localhost:9999/path", "http://localhost:9999");
|
|
|
|
// convert host to lower case
|
|
this.OriginHeader("//LOCALHOST/", "http://localhost");
|
|
}
|
|
|
|
void HostHeader(string uri, string expected) =>
|
|
this.HeaderDefaultHttp(uri, HttpHeaderNames.Host, expected);
|
|
|
|
void OriginHeader(string uri, string expected) =>
|
|
this.HeaderDefaultHttp(uri, this.GetOriginHeaderName(), expected);
|
|
|
|
protected void HeaderDefaultHttp(string uri, AsciiString header, string expectedValue)
|
|
{
|
|
Assert.True(Uri.TryCreate(uri, UriKind.RelativeOrAbsolute, out Uri originalUri));
|
|
WebSocketClientHandshaker handshaker = this.NewHandshaker(originalUri);
|
|
IFullHttpRequest request = handshaker.NewHandshakeRequest();
|
|
try
|
|
{
|
|
Assert.True(request.Headers.TryGet(header, out ICharSequence value));
|
|
Assert.Equal(expectedValue, value.ToString(), true);
|
|
}
|
|
finally
|
|
{
|
|
request.Release();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void RawPath()
|
|
{
|
|
var uri = new Uri("ws://localhost:9999/path%20with%20ws");
|
|
WebSocketClientHandshaker handshaker = this.NewHandshaker(uri);
|
|
IFullHttpRequest request = handshaker.NewHandshakeRequest();
|
|
try
|
|
{
|
|
Assert.Equal("/path%20with%20ws", request.Uri);
|
|
}
|
|
finally
|
|
{
|
|
request.Release();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void RawPathWithQuery()
|
|
{
|
|
var uri = new Uri("ws://localhost:9999/path%20with%20ws?a=b%20c");
|
|
WebSocketClientHandshaker handshaker = this.NewHandshaker(uri);
|
|
IFullHttpRequest request = handshaker.NewHandshakeRequest();
|
|
try
|
|
{
|
|
Assert.Equal("/path%20with%20ws?a=b%20c", request.Uri);
|
|
}
|
|
finally
|
|
{
|
|
request.Release();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HttpResponseAndFrameInSameBuffer() => this.TestHttpResponseAndFrameInSameBuffer(false);
|
|
|
|
[Fact]
|
|
public void HttpResponseAndFrameInSameBufferCodec() => this.TestHttpResponseAndFrameInSameBuffer(true);
|
|
|
|
void TestHttpResponseAndFrameInSameBuffer(bool codec)
|
|
{
|
|
string url = "ws://localhost:9999/ws";
|
|
WebSocketClientHandshaker shaker = this.NewHandshaker(new Uri(url));
|
|
var handshaker = new Handshaker(shaker);
|
|
|
|
var data = new byte[24];
|
|
PlatformDependent.GetThreadLocalRandom().NextBytes(data);
|
|
|
|
// Create a EmbeddedChannel which we will use to encode a BinaryWebsocketFrame to bytes and so use these
|
|
// to test the actual handshaker.
|
|
var factory = new WebSocketServerHandshakerFactory(url, null, false);
|
|
WebSocketServerHandshaker socketServerHandshaker = factory.NewHandshaker(shaker.NewHandshakeRequest());
|
|
var websocketChannel = new EmbeddedChannel(socketServerHandshaker.NewWebSocketEncoder(),
|
|
socketServerHandshaker.NewWebsocketDecoder());
|
|
Assert.True(websocketChannel.WriteOutbound(new BinaryWebSocketFrame(Unpooled.WrappedBuffer(data))));
|
|
|
|
byte[] bytes = Encoding.ASCII.GetBytes("HTTP/1.1 101 Switching Protocols\r\nContent-Length: 0\r\n\r\n");
|
|
|
|
CompositeByteBuffer compositeByteBuf = Unpooled.CompositeBuffer();
|
|
compositeByteBuf.AddComponent(true, Unpooled.WrappedBuffer(bytes));
|
|
for (;;)
|
|
{
|
|
var frameBytes = websocketChannel.ReadOutbound<IByteBuffer>();
|
|
if (frameBytes == null)
|
|
{
|
|
break;
|
|
}
|
|
compositeByteBuf.AddComponent(true, frameBytes);
|
|
}
|
|
|
|
var ch = new EmbeddedChannel(new HttpObjectAggregator(int.MaxValue), new Handler(handshaker));
|
|
if (codec)
|
|
{
|
|
ch.Pipeline.AddFirst(new HttpClientCodec());
|
|
}
|
|
else
|
|
{
|
|
ch.Pipeline.AddFirst(new HttpRequestEncoder(), new HttpResponseDecoder());
|
|
}
|
|
|
|
// We need to first write the request as HttpClientCodec will fail if we receive a response before a request
|
|
// was written.
|
|
shaker.HandshakeAsync(ch).Wait();
|
|
for (;;)
|
|
{
|
|
// Just consume the bytes, we are not interested in these.
|
|
var buf = ch.ReadOutbound<IByteBuffer>();
|
|
if (buf == null)
|
|
{
|
|
break;
|
|
}
|
|
buf.Release();
|
|
}
|
|
Assert.True(ch.WriteInbound(compositeByteBuf));
|
|
Assert.True(ch.Finish());
|
|
|
|
var frame = ch.ReadInbound<BinaryWebSocketFrame>();
|
|
IByteBuffer expect = Unpooled.WrappedBuffer(data);
|
|
try
|
|
{
|
|
Assert.Equal(expect, frame.Content);
|
|
Assert.True(frame.IsFinalFragment);
|
|
Assert.Equal(0, frame.Rsv);
|
|
}
|
|
finally
|
|
{
|
|
expect.Release();
|
|
frame.Release();
|
|
}
|
|
}
|
|
|
|
sealed class Handshaker : WebSocketClientHandshaker
|
|
{
|
|
readonly WebSocketClientHandshaker shaker;
|
|
|
|
public Handshaker(WebSocketClientHandshaker shaker)
|
|
: base(shaker.Uri, shaker.Version, null, EmptyHttpHeaders.Default, int.MaxValue)
|
|
{
|
|
this.shaker = shaker;
|
|
}
|
|
|
|
protected internal override IFullHttpRequest NewHandshakeRequest() => this.shaker.NewHandshakeRequest();
|
|
|
|
protected override void Verify(IFullHttpResponse response)
|
|
{
|
|
// Not do any verification, so we do not need to care sending the correct headers etc in the test,
|
|
// which would just make things more complicated.
|
|
}
|
|
|
|
protected internal override IWebSocketFrameDecoder NewWebSocketDecoder() => this.shaker.NewWebSocketDecoder();
|
|
|
|
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => this.shaker.NewWebSocketEncoder();
|
|
}
|
|
|
|
sealed class Handler : SimpleChannelInboundHandler<IFullHttpResponse>
|
|
{
|
|
readonly Handshaker handshaker;
|
|
|
|
public Handler(Handshaker handshaker)
|
|
{
|
|
this.handshaker = handshaker;
|
|
}
|
|
|
|
protected override void ChannelRead0(IChannelHandlerContext ctx, IFullHttpResponse msg)
|
|
{
|
|
this.handshaker.FinishHandshake(ctx.Channel, msg);
|
|
ctx.Channel.Pipeline.Remove(this);
|
|
}
|
|
}
|
|
}
|
|
}
|