public void RequestTarget_AuthorityForm_without_port() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "CONNECT www.example.org HTTP/1.1", "Host: www.example.org:400", // dare to give different value from request-target for test "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { // HostEndPoint and Host are derived from the request-target. Assert.Equal(new DnsEndPoint("www.example.org", 443), request.HostEndPoint); Assert.Equal("www.example.org:443", request.Host); // HostSpan is the span of the actual Host field. Assert.Equal(new Span(34, 61), request.HostSpan); // TargetUri is null for request-target of authority-form Assert.Equal(null, request.TargetUri); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void RequestTarget_AbsoluteForm() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET http://www.example.org/abc/def?ghij=kl HTTP/1.1", "Host: test.example.org:123", // dare to give different value from request-target for test "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { // HostEndPoint and Host are derived from the request-target. Assert.Equal(new DnsEndPoint("www.example.org", 80), request.HostEndPoint); Assert.Equal("www.example.org:80", request.Host); // HostSpan is the span of the actual Host field. Assert.Equal(new Span(53, 81), request.HostSpan); Assert.Equal(new Uri("http://www.example.org/abc/def?ghij=kl"), request.TargetUri); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Redirect_Chunked() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE // Redirect() supports chunked transfer sample.CheckChunkFlushing = true; sample.AppendHeader( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "" ); // write chunked body sample.AppendSimpleChunk(0x100); sample.AppendSimpleChunk(0x100); sample.AppendSimpleChunk(0x100); sample.AppendLastChunk(); // ACT & ASSERT int actualMessageCount = TestReadHeaderAndRedirect(sample, (response) => { Assert.Equal(-1, response.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Body_Small() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE const long bodyLength = ComponentFactory.MemoryBlockCache.MemoryBlockSize - 10; sample.AppendHeader( "HTTP/1.1 200 OK", $"Content-Length: {bodyLength}", "" ); sample.AppendRandomData(bodyLength); // The body length should be in range of 'small' length. // That is, // * It cannot be stored in the rest of header's memory block, but // * It can be stored in one memory block. // In a Response object, 'small' length body is stored in a memory block. Debug.Assert(ComponentFactory.MemoryBlockCache.MemoryBlockSize < sample.SampleWriterPosition); Debug.Assert(bodyLength <= ComponentFactory.MemoryBlockCache.MemoryBlockSize); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(bodyLength, response.ContentLength); }); sample.AssertOutputEqualToSample(); Assert.Equal(1, actualMessageCount); } }
protected int TestReadAndWrite(MessageSample sample, Action <TMessage> handler = null, Request request = null, bool suppressModification = false) { // argument checks Debug.Assert(sample != null); sample.CompleteArranging(); // handler can be null // request can be null // state checks IAdapter adapter = this.Adapter; Debug.Assert(adapter != null); // create a Message and call Read and Write int messageCount = 0; using (TMessage message = adapter.Create(sample)) { Assert.Equal(MessageReadingState.None, message.ReadingState); // Read while (adapter.Read(message, request)) { Assert.Equal(MessageReadingState.Body, message.ReadingState); ++messageCount; // handle the message handler?.Invoke(message); // Write adapter.Write(message, suppressModification); } } return(messageCount); }
public void Body_Large() { using (MessageSample sample = CreateMessageSample(largeMessage: true)) { // ARRANGE const long bodyLength = BodyBuffer.BodyStreamThreshold * 2; sample.AppendHeader( "HTTP/1.1 200 OK", $"Content-Length: {bodyLength}", "" ); sample.AppendRandomData(bodyLength); // The body length should be in range of 'large' length. // That is, // * It must be larger than BodyBuffer.BodyStreamThreshold. // In a Response object, 'large' length body is stored in a FileStream. Debug.Assert(BodyBuffer.BodyStreamThreshold < bodyLength); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(bodyLength, response.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Modification_suppressModification() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET http://www.example.org/test/index.html?abc=def HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { request.AddModification( request.RequestTargetSpan, (modifier) => { modifier.WriteASCIIString(request.TargetUri.PathAndQuery); return(true); } ); }, request: null, suppressModification: true); // suppressModification! Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Body_Chunked_Large() { using (MessageSample sample = CreateMessageSample(largeMessage: true)) { // ARRANGE // The sample.CheckChunkFlushing is not meaningful with Write() method, // because Write() writes stored body at once. Debug.Assert(sample.CheckChunkFlushing == false); sample.AppendHeader( "HTTP/1.1 200 OK", "Transfer-Encoding: chunked", "" ); // write chunked body for (int i = 0; i < 1024; ++i) { sample.AppendSimpleChunk(1024); } sample.AppendLastChunk(); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(-1, response.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Modification_remove() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "Proxy-Authorization: dXNlcjpwYXNz", "" ); sample.AppendBody(MessageSample.EmptyBody); string expectedOutput = string.Join( MessageSample.CRLF, // separator "GET / HTTP/1.1", "Host: www.example.org", "", MessageSample.EmptyBody ); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { // remove the existing field request.AddModification( request.ProxyAuthorizationSpan, (modifier) => true // remove ); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualTo(expectedOutput); } }
public void Redirect_Medium() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE const long bodyLength = 10 * 1024; // 10k sample.AppendHeader( "HTTP/1.1 200 OK", $"Content-Length: {bodyLength}", "" ); sample.AppendRandomData(bodyLength); // The body length should be in range of 'medium' length. // That is, // * It must be larger than the memory block size, and // * It must be smaller than or equal to BodyBuffer.BodyStreamThreshold. // In a Response object, 'medium' length body is stored in a MemoryStream. Debug.Assert(ComponentFactory.MemoryBlockCache.MemoryBlockSize < bodyLength); Debug.Assert(bodyLength <= BodyBuffer.BodyStreamThreshold); // ACT & ASSERT int actualMessageCount = TestReadHeaderAndRedirect(sample, (response) => { Assert.Equal(bodyLength, response.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Header_Large() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.1 200 OK" ); for (int i = 0; i < 50; ++i) { sample.AppendText($"X-Test-{i}: 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", appendCRLF: true); } sample.AppendHeader( "" ); // This sample header to be tested must consume more than two memory blocks. Debug.Assert(ComponentFactory.MemoryBlockCache.MemoryBlockSize < sample.SampleWriterPosition); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(200, response.StatusCode); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Modification_change() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET http://www.example.org/test/index.html?abc=def HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); string expectedOutput = string.Join( MessageSample.CRLF, // separator "GET /test/index.html?abc=def HTTP/1.1", "Host: www.example.org", "", MessageSample.EmptyBody ); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { // change the existing field request.AddModification( request.RequestTargetSpan, (modifier) => { modifier.WriteASCIIString(request.TargetUri.PathAndQuery); return(true); } ); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualTo(expectedOutput); } }
public void Modification_append() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); string expectedOutput = string.Join( MessageSample.CRLF, // separator "GET / HTTP/1.1", "Host: www.example.org", "X-Test: dummy", "", MessageSample.EmptyBody ); Func <Modifier, bool> handler = (modifier) => { modifier.WriteASCIIString("X-Test: dummy", appendCRLF: true); return(true); }; // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { // append a field request.AddModification(request.EndOfHeaderFields, handler); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualTo(expectedOutput); } }
public void Body_Chunked_with_Trailers() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE // The sample.CheckChunkFlushing is not meaningful with Write() method, // because Write() writes stored body at once. Debug.Assert(sample.CheckChunkFlushing == false); sample.AppendHeader( "PUT / HTTP/1.1", "Host: www.example.org:80", "Transfer-Encoding: chunked", "" ); // write chunked body with trailers sample.AppendLastChunk( "X-Test-1: dummy", "X-Test-2: dummy" ); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Assert.Equal(-1, request.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Body_Chunked() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE // The sample.CheckChunkFlushing is not meaningful with Write() method, // because Write() writes stored body at once. Debug.Assert(sample.CheckChunkFlushing == false); sample.AppendHeader( "PUT / HTTP/1.1", "Host: www.example.org:80", "Transfer-Encoding: chunked", "" ); // write chunked body sample.AppendChunkSizeLine("2F"); // chunk-size, simple sample.AppendRandomChunkData(0x2F); // chunk-data sample.AppendChunkSizeLine("08"); // chunk-size, in redundant format sample.AppendRandomChunkData(0x08); // chunk-data sample.AppendChunkSizeLine("000"); // last-chunk, in redundant format sample.AppendCRLF(); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Assert.Equal(-1, request.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Body_Tiny() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "PUT / HTTP/1.1", "Host: www.example.org:80", "Content-Length: 5", "" ); sample.AppendBody("12345"); // The body length should be in range of 'tiny' length. // That is, the whole message must be stored in one memory block. // In a Request object, 'tiny' length body is stored in the rest of header's memory block. Debug.Assert(sample.SampleWriterPosition < ComponentFactory.MemoryBlockCache.MemoryBlockSize); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Assert.Equal(5, request.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void PrefetchedBytes_ReadHeaderAndSkipBody() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "Content-Length: 3", "" ); sample.AppendBody("ABC"); // insert 0-length body message in the middle of sequence // In prefetched bytes processing, 0-length body handling is mistakable. sample.AppendHeader( "HEAD / HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); sample.AppendHeader( "CONNECT www.example.org:443 HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // The body length should be in range of 'tiny' length. // "prefetched bytes" occurs when the body is 'tiny' length for simple body. // In this case, whole three messages are on one memory block. Debug.Assert(sample.SampleWriterPosition < ComponentFactory.MemoryBlockCache.MemoryBlockSize); // ACT & ASSERT int counter = 0; int actualMessageCount = TestReadHeaderAndSkipBody(sample, (request) => { switch (counter) { case 0: Assert.Equal("GET", request.Method); Assert.Equal(3, request.ContentLength); break; case 1: Assert.Equal("HEAD", request.Method); Assert.Equal(0, request.ContentLength); break; case 2: Assert.Equal("CONNECT", request.Method); Assert.Equal(0, request.ContentLength); break; } ++counter; }); Assert.Equal(3, actualMessageCount); sample.AssertAllSampleBytesRead(); } }
public void PrefetchedBytes_ReadHeaderAndSkipBody() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.1 200 OK", "Content-Length: 7", "" ); sample.AppendBody("1234567"); // insert 0-length body message in the middle of sequence // In prefetched bytes processing, 0-length body handling is mistakable. sample.AppendHeader( "HTTP/1.1 302 Found", "" ); sample.AppendBody(MessageSample.EmptyBody); sample.AppendHeader( "HTTP/1.1 404 Not Found", "Content-Length: 0", "" ); sample.AppendBody(MessageSample.EmptyBody); // The body length should be in range of 'tiny' length. // "prefetched bytes" occurs when the body is 'tiny' length for simple body. // In this case, whole three messages are on one memory block. Debug.Assert(sample.SampleWriterPosition < ComponentFactory.MemoryBlockCache.MemoryBlockSize); // ACT & ASSERT int counter = 0; int actualMessageCount = TestReadHeaderAndSkipBody(sample, (response) => { switch (counter) { case 0: Assert.Equal(200, response.StatusCode); Assert.Equal(7, response.ContentLength); break; case 1: Assert.Equal(302, response.StatusCode); Assert.Equal(0, response.ContentLength); break; case 2: Assert.Equal(404, response.StatusCode); Assert.Equal(0, response.ContentLength); break; } ++counter; }); Assert.Equal(3, actualMessageCount); sample.AssertAllSampleBytesRead(); } }
public AdapterStream(MessageSample owner, Stream innerStream) { // argument checks Debug.Assert(owner != null); Debug.Assert(innerStream != null); // initialize members this.owner = owner; this.innerStream = innerStream; return; }
public void Modification_append_samepoint() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // keep the order of X-Test-1, X-Test-2 and X-Test-3. string expectedOutput = string.Join( MessageSample.CRLF, // separator "GET / HTTP/1.1", "Host: www.example.org", "X-Test-1: dummy", "X-Test-2: dummy", "X-Test-3: dummy", "", MessageSample.EmptyBody ); Func <Modifier, bool> handler1 = (modifier) => { modifier.WriteASCIIString("X-Test-1: dummy", appendCRLF: true); return(true); }; Func <Modifier, bool> handler2 = (modifier) => { modifier.WriteASCIIString("X-Test-2: dummy", appendCRLF: true); return(true); }; Func <Modifier, bool> handler3 = (modifier) => { modifier.WriteASCIIString("X-Test-3: dummy", appendCRLF: true); return(true); }; // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Span span = request.EndOfHeaderFields; Debug.Assert(span.Length == 0); // add at the same point request.AddModification(span, handler1); request.AddModification(span, handler2); request.AddModification(span, handler3); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualTo(expectedOutput); } }
public void Version_LowerCaseHTTPName() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "http/1.1 200 OK", // HTTP-version: invalid! "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT HttpException exception = Assert.Throws <HttpException>( () => TestReadAndWrite(sample) ); Assert.Equal(HttpStatusCode.BadGateway, exception.HttpStatusCode); } }
public void StatusCode_TooShortDigits() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.1 20 Invalid Status Code", // status-code: too short digits! "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT HttpException exception = Assert.Throws <HttpException>( () => TestReadAndWrite(sample) ); Assert.Equal(HttpStatusCode.BadGateway, exception.HttpStatusCode); } }
public void Version_InvalidDigits() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1.2", // HTTP-version: invalid! "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT HttpException actual = Assert.Throws <HttpException>( () => TestReadAndWrite(sample) ); Assert.Equal(HttpStatusCode.BadRequest, actual.HttpStatusCode); } }
public void StatusCode_407() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.1 407 Proxy Authentication Required", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(407, response.StatusCode); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void SkipBody() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.1 200 OK", "Content-Length: 7", "" ); sample.AppendBody("ABCDEFG"); // ACT & ASSERT int actualMessageCount = TestReadHeaderAndSkipBody(sample, (response) => { Assert.Equal(7, response.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertAllSampleBytesRead(); } }
public void Version_10() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "HTTP/1.0 200 OK", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (response) => { Assert.Equal(new Version(1, 0), response.Version); Assert.Equal(200, response.StatusCode); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Method_CONNECT() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "CONNECT www.example.org:443 HTTP/1.1", "Host: www.example.org:443", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Assert.Equal("CONNECT", request.Method); Assert.Equal(true, request.IsConnectMethod); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void Simple() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { Assert.Equal("GET", request.Method); Assert.Equal(new Version(1, 1), request.Version); Assert.Equal("www.example.org:80", request.Host); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }
public void SkipBody() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE const long bodyLength = 512; sample.AppendHeader( "PUT / HTTP/1.1", "Host: www.example.org:80", $"Content-Length: {bodyLength}", "" ); sample.AppendRandomData(bodyLength); // ACT & ASSERT int actualMessageCount = TestReadHeaderAndSkipBody(sample, (request) => { Assert.Equal(bodyLength, request.ContentLength); }); Assert.Equal(1, actualMessageCount); sample.AssertAllSampleBytesRead(); } }
public void Modification_cancel() { using (MessageSample sample = CreateMessageSample()) { // ARRANGE sample.AppendHeader( "GET / HTTP/1.1", "Host: www.example.org", "" ); sample.AppendBody(MessageSample.EmptyBody); // ACT & ASSERT int actualMessageCount = TestReadAndWrite(sample, (request) => { request.AddModification( request.HostSpan, (modifier) => false // cancel modification! ); }); Assert.Equal(1, actualMessageCount); sample.AssertOutputEqualToSample(); } }