A game framework written with osu! in mind.
at master 822 lines 28 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Diagnostics; 7using System.Net; 8using System.Net.Http; 9using System.Reflection; 10using System.Threading; 11using System.Threading.Tasks; 12using Newtonsoft.Json; 13using NUnit.Framework; 14using osu.Framework.Graphics; 15using osu.Framework.IO.Network; 16using WebRequest = osu.Framework.IO.Network.WebRequest; 17 18namespace osu.Framework.Tests.IO 19{ 20 [TestFixture] 21 [Category("httpbin")] 22 public class TestWebRequest 23 { 24 private const string default_protocol = "http"; 25 private const string invalid_get_url = "a.ppy.shhhhh"; 26 27 private static readonly string host; 28 private static readonly IEnumerable<string> protocols; 29 30 static TestWebRequest() 31 { 32 bool localHttpBin = Environment.GetEnvironmentVariable("LocalHttpBin")?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false; 33 34 if (localHttpBin) 35 { 36 // httpbin very frequently falls over and causes random tests to fail 37 // Thus appveyor builds rely on a local httpbin instance to run the tests 38 39 host = "127.0.0.1"; 40 protocols = new[] { default_protocol }; 41 } 42 else 43 { 44 host = "httpbin.org"; 45 protocols = new[] { default_protocol, "https" }; 46 } 47 } 48 49 [Test, Retry(5)] 50 public void TestValidGet([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async) 51 { 52 var url = $"{protocol}://{host}/get"; 53 var request = new JsonWebRequest<HttpBinGetResponse>(url) 54 { 55 Method = HttpMethod.Get, 56 AllowInsecureRequests = true 57 }; 58 59 testValidGetInternal(async, request, "osu-framework"); 60 } 61 62 [Test, Retry(5)] 63 public void TestCustomUserAgent([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async) 64 { 65 var url = $"{protocol}://{host}/get"; 66 var request = new CustomUserAgentWebRequest(url) 67 { 68 Method = HttpMethod.Get, 69 AllowInsecureRequests = true 70 }; 71 72 testValidGetInternal(async, request, "custom-ua"); 73 } 74 75 private static void testValidGetInternal(bool async, JsonWebRequest<HttpBinGetResponse> request, string expectedUserAgent) 76 { 77 bool hasThrown = false; 78 request.Failed += exception => hasThrown = exception != null; 79 80 if (async) 81 Assert.DoesNotThrowAsync(request.PerformAsync); 82 else 83 Assert.DoesNotThrow(request.Perform); 84 85 Assert.IsTrue(request.Completed); 86 Assert.IsFalse(request.Aborted); 87 88 var responseObject = request.ResponseObject; 89 90 Assert.IsTrue(responseObject != null); 91 Assert.IsTrue(responseObject.Headers.UserAgent == expectedUserAgent); 92 93 // disabled due to hosted version returning incorrect response (https://github.com/postmanlabs/httpbin/issues/545) 94 // Assert.AreEqual(url, responseObject.Url); 95 96 Assert.IsFalse(hasThrown); 97 } 98 99 /// <summary> 100 /// Tests async execution is correctly yielding during IO wait time. 101 /// </summary> 102 [Test] 103 public void TestConcurrency() 104 { 105 const int request_count = 10; 106 const int induced_delay = 5; 107 108 int finished = 0; 109 int failed = 0; 110 int started = 0; 111 112 Stopwatch sw = new Stopwatch(); 113 sw.Start(); 114 115 List<long> startTimes = new List<long>(); 116 117 List<Task> running = new List<Task>(); 118 119 for (int i = 0; i < request_count; i++) 120 { 121 var request = new DelayedWebRequest 122 { 123 Method = HttpMethod.Get, 124 AllowInsecureRequests = true, 125 Delay = induced_delay 126 }; 127 128 request.Started += () => 129 { 130 Interlocked.Increment(ref started); 131 lock (startTimes) 132 startTimes.Add(sw.ElapsedMilliseconds); 133 }; 134 request.Finished += () => Interlocked.Increment(ref finished); 135 request.Failed += _ => 136 { 137 Interlocked.Increment(ref failed); 138 Interlocked.Increment(ref finished); 139 }; 140 141 running.Add(request.PerformAsync()); 142 } 143 144 Task.WaitAll(running.ToArray()); 145 146 Assert.Zero(failed); 147 148 // in the case threads are not yielding, the time taken will be greater than double the induced delay (after considering latency). 149 Assert.Less(sw.ElapsedMilliseconds, induced_delay * 2 * 1000); 150 151 Assert.AreEqual(request_count, started); 152 153 Assert.AreEqual(request_count, finished); 154 155 Assert.AreEqual(request_count, startTimes.Count); 156 157 // another case would be requests starting too late into the test. just to make sure. 158 for (int i = 0; i < request_count; i++) 159 Assert.Less(startTimes[i] - startTimes[0], induced_delay * 1000); 160 } 161 162 [Test, Retry(5)] 163 public void TestInvalidGetExceptions([ValueSource(nameof(protocols))] string protocol, [Values(true, false)] bool async) 164 { 165 var request = new WebRequest($"{protocol}://{invalid_get_url}") 166 { 167 Method = HttpMethod.Get, 168 AllowInsecureRequests = true 169 }; 170 171 Exception finishedException = null; 172 request.Failed += exception => finishedException = exception; 173 174 if (async) 175 Assert.ThrowsAsync<HttpRequestException>(request.PerformAsync); 176 else 177 Assert.Throws<HttpRequestException>(request.Perform); 178 179 Assert.IsTrue(request.Completed); 180 Assert.IsTrue(request.Aborted); 181 182 Assert.IsTrue(request.GetResponseString() == null); 183 Assert.IsNotNull(finishedException); 184 } 185 186 [Test, Retry(5)] 187 public void TestBadStatusCode([Values(true, false)] bool async) 188 { 189 var request = new WebRequest($"{default_protocol}://{host}/hidden-basic-auth/user/passwd") 190 { 191 AllowInsecureRequests = true, 192 }; 193 194 bool hasThrown = false; 195 request.Failed += exception => hasThrown = exception != null; 196 197 if (async) 198 Assert.ThrowsAsync<WebException>(request.PerformAsync); 199 else 200 Assert.Throws<WebException>(request.Perform); 201 202 Assert.IsTrue(request.Completed); 203 Assert.IsTrue(request.Aborted); 204 205 Assert.IsEmpty(request.GetResponseString()); 206 207 Assert.IsTrue(hasThrown); 208 } 209 210 [Test, Retry(5)] 211 public void TestJsonWebRequestThrowsCorrectlyOnMultipleErrors([Values(true, false)] bool async) 212 { 213 var request = new JsonWebRequest<Drawable>("badrequest://www.google.com") 214 { 215 AllowInsecureRequests = true, 216 }; 217 218 bool hasThrown = false; 219 request.Failed += exception => hasThrown = exception != null; 220 221 if (async) 222 Assert.ThrowsAsync<ArgumentException>(request.PerformAsync); 223 else 224 Assert.Throws<ArgumentException>(request.Perform); 225 226 Assert.IsTrue(request.Completed); 227 Assert.IsTrue(request.Aborted); 228 229 Assert.IsNull(request.GetResponseString()); 230 Assert.IsNull(request.ResponseObject); 231 232 Assert.IsTrue(hasThrown); 233 } 234 235 /// <summary> 236 /// Tests aborting the <see cref="WebRequest"/> after response has been received from the server 237 /// but before data has been read. 238 /// </summary> 239 [Test, Retry(5)] 240 public void TestAbortReceive([Values(true, false)] bool async) 241 { 242 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 243 { 244 Method = HttpMethod.Get, 245 AllowInsecureRequests = true, 246 }; 247 248 bool hasThrown = false; 249 request.Failed += exception => hasThrown = exception != null; 250 request.Started += () => request.Abort(); 251 252 if (async) 253 Assert.DoesNotThrowAsync(request.PerformAsync); 254 else 255 Assert.DoesNotThrow(request.Perform); 256 257 Assert.IsTrue(request.Completed); 258 Assert.IsTrue(request.Aborted); 259 260 Assert.IsTrue(request.ResponseObject == null); 261 262 Assert.IsFalse(hasThrown); 263 } 264 265 /// <summary> 266 /// Tests aborting the <see cref="WebRequest"/> before the request is sent to the server. 267 /// </summary> 268 [Test, Retry(5)] 269 public void TestAbortRequest() 270 { 271 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 272 { 273 Method = HttpMethod.Get, 274 AllowInsecureRequests = true, 275 }; 276 277 bool hasThrown = false; 278 request.Failed += exception => hasThrown = exception != null; 279 280#pragma warning disable 4014 281 request.PerformAsync(); 282#pragma warning restore 4014 283 284 Assert.DoesNotThrow(request.Abort); 285 286 Assert.IsTrue(request.Completed); 287 Assert.IsTrue(request.Aborted); 288 289 Assert.IsTrue(request.ResponseObject == null); 290 291 Assert.IsFalse(hasThrown); 292 } 293 294 /// <summary> 295 /// Tests being able to abort + restart a request. 296 /// </summary> 297 [Test, Retry(5)] 298 public void TestRestartAfterAbort([Values(true, false)] bool async) 299 { 300 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 301 { 302 Method = HttpMethod.Get, 303 AllowInsecureRequests = true, 304 }; 305 306 bool hasThrown = false; 307 request.Failed += exception => hasThrown = exception != null; 308 309#pragma warning disable 4014 310 request.PerformAsync(); 311#pragma warning restore 4014 312 313 Assert.DoesNotThrow(request.Abort); 314 315 if (async) 316 Assert.ThrowsAsync<InvalidOperationException>(request.PerformAsync); 317 else 318 Assert.Throws<InvalidOperationException>(request.Perform); 319 320 Assert.IsTrue(request.Completed); 321 Assert.IsTrue(request.Aborted); 322 323 var responseObject = request.ResponseObject; 324 325 Assert.IsTrue(responseObject == null); 326 Assert.IsFalse(hasThrown); 327 } 328 329 /// <summary> 330 /// Tests cancelling the <see cref="WebRequest"/> after response has been received from the server 331 /// but before data has been read. 332 /// </summary> 333 [Test, Retry(5)] 334 public void TestCancelReceive() 335 { 336 var cancellationSource = new CancellationTokenSource(); 337 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 338 { 339 Method = HttpMethod.Get, 340 AllowInsecureRequests = true, 341 }; 342 343 bool hasThrown = false; 344 request.Failed += exception => hasThrown = exception != null; 345 request.Started += () => cancellationSource.Cancel(); 346 347 Assert.DoesNotThrowAsync(() => request.PerformAsync(cancellationSource.Token)); 348 349 Assert.IsTrue(request.Completed); 350 Assert.IsTrue(request.Aborted); 351 352 Assert.IsTrue(request.ResponseObject == null); 353 Assert.IsFalse(hasThrown); 354 } 355 356 /// <summary> 357 /// Tests aborting the <see cref="WebRequest"/> before the request is sent to the server. 358 /// </summary> 359 [Test, Retry(5)] 360 public async Task TestCancelRequest() 361 { 362 var cancellationSource = new CancellationTokenSource(); 363 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 364 { 365 Method = HttpMethod.Get, 366 AllowInsecureRequests = true, 367 }; 368 369 bool hasThrown = false; 370 request.Failed += exception => hasThrown = exception != null; 371 372 cancellationSource.Cancel(); 373 await request.PerformAsync(cancellationSource.Token).ConfigureAwait(false); 374 375 Assert.IsTrue(request.Completed); 376 Assert.IsTrue(request.Aborted); 377 378 Assert.IsTrue(request.ResponseObject == null); 379 380 Assert.IsFalse(hasThrown); 381 } 382 383 /// <summary> 384 /// Tests being able to cancel + restart a request. 385 /// </summary> 386 [Test, Retry(5)] 387 public void TestRestartAfterAbort() 388 { 389 var cancellationSource = new CancellationTokenSource(); 390 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 391 { 392 Method = HttpMethod.Get, 393 AllowInsecureRequests = true, 394 }; 395 396 bool hasThrown = false; 397 request.Failed += exception => hasThrown = exception != null; 398 399 cancellationSource.Cancel(); 400 request.PerformAsync(cancellationSource.Token); 401 402 Assert.ThrowsAsync<InvalidOperationException>(request.PerformAsync); 403 404 Assert.IsTrue(request.Completed); 405 Assert.IsTrue(request.Aborted); 406 407 var responseObject = request.ResponseObject; 408 409 Assert.IsTrue(responseObject == null); 410 Assert.IsFalse(hasThrown); 411 } 412 413 /// <summary> 414 /// Tests that specifically-crafted <see cref="WebRequest"/> is completed after one timeout. 415 /// </summary> 416 [Test, Retry(5)] 417 public void TestOneTimeout() 418 { 419 var request = new DelayedWebRequest 420 { 421 Method = HttpMethod.Get, 422 AllowInsecureRequests = true, 423 Timeout = 1000, 424 Delay = 2 425 }; 426 427 Exception thrownException = null; 428 request.Failed += e => thrownException = e; 429 request.CompleteInvoked = () => request.Delay = 0; 430 431 Assert.DoesNotThrow(request.Perform); 432 433 Assert.IsTrue(request.Completed); 434 Assert.IsFalse(request.Aborted); 435 436 Assert.IsTrue(thrownException == null); 437 Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount); 438 } 439 440 /// <summary> 441 /// Tests that a <see cref="WebRequest"/> will only timeout a maximum of <see cref="WebRequest.MAX_RETRIES"/> times before being aborted. 442 /// </summary> 443 [Test, Retry(5)] 444 public void TestFailTimeout() 445 { 446 var request = new WebRequest($"{default_protocol}://{host}/delay/4") 447 { 448 Method = HttpMethod.Get, 449 AllowInsecureRequests = true, 450 Timeout = 1000 451 }; 452 453 Exception thrownException = null; 454 request.Failed += e => thrownException = e; 455 456 Assert.Throws<WebException>(request.Perform); 457 458 Assert.IsTrue(request.Completed); 459 Assert.IsTrue(request.Aborted); 460 461 Assert.IsTrue(thrownException != null); 462 Assert.AreEqual(WebRequest.MAX_RETRIES, request.RetryCount); 463 Assert.AreEqual(typeof(WebException), thrownException.GetType()); 464 } 465 466 /// <summary> 467 /// Tests being able to abort + restart a request. 468 /// </summary> 469 [Test, Retry(5)] 470 public void TestEventUnbindOnCompletion([Values(true, false)] bool async) 471 { 472 var request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 473 { 474 Method = HttpMethod.Get, 475 AllowInsecureRequests = true, 476 }; 477 478 request.Started += () => { }; 479 request.Failed += e => { }; 480 request.DownloadProgress += (l1, l2) => { }; 481 request.UploadProgress += (l1, l2) => { }; 482 483 Assert.DoesNotThrow(request.Perform); 484 485 var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public); 486 487 foreach (var e in events) 488 { 489 var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public); 490 Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0); 491 } 492 } 493 494 /// <summary> 495 /// Tests being able to abort + restart a request. 496 /// </summary> 497 [Test, Retry(5)] 498 public void TestUnbindOnDispose([Values(true, false)] bool async) 499 { 500 WebRequest request; 501 502 using (request = new JsonWebRequest<HttpBinGetResponse>($"{default_protocol}://{host}/get") 503 { 504 Method = HttpMethod.Get, 505 AllowInsecureRequests = true, 506 }) 507 { 508 request.Started += () => { }; 509 request.Failed += e => { }; 510 request.DownloadProgress += (l1, l2) => { }; 511 request.UploadProgress += (l1, l2) => { }; 512 513 Assert.DoesNotThrow(request.Perform); 514 } 515 516 var events = request.GetType().GetEvents(BindingFlags.Instance | BindingFlags.Public); 517 518 foreach (var e in events) 519 { 520 var field = request.GetType().GetField(e.Name, BindingFlags.Instance | BindingFlags.Public); 521 Assert.IsFalse(((Delegate)field?.GetValue(request))?.GetInvocationList().Length > 0); 522 } 523 } 524 525 [Test, Retry(5)] 526 public void TestGetWithQueryStringParameters() 527 { 528 const string test_key_1 = "testkey1"; 529 const string test_val_1 = "testval1 that ends with a #"; 530 531 const string test_key_2 = "testkey2"; 532 const string test_val_2 = "testval2 that ends with a space "; 533 534 var request = new JsonWebRequest<HttpBinGetResponse>($@"{default_protocol}://{host}/get") 535 { 536 Method = HttpMethod.Get, 537 AllowInsecureRequests = true 538 }; 539 540 request.AddParameter(test_key_1, test_val_1); 541 request.AddParameter(test_key_2, test_val_2); 542 543 Assert.DoesNotThrow(request.Perform); 544 545 var responseObject = request.ResponseObject; 546 547 Assert.IsTrue(request.Completed); 548 Assert.IsFalse(request.Aborted); 549 550 Assert.NotNull(responseObject.Arguments); 551 552 Assert.True(responseObject.Arguments.ContainsKey(test_key_1)); 553 Assert.AreEqual(test_val_1, responseObject.Arguments[test_key_1]); 554 555 Assert.True(responseObject.Arguments.ContainsKey(test_key_2)); 556 Assert.AreEqual(test_val_2, responseObject.Arguments[test_key_2]); 557 } 558 559 [Test, Retry(5)] 560 public void TestPostWithJsonResponse([Values(true, false)] bool async) 561 { 562 var request = new JsonWebRequest<HttpBinPostResponse>($"{default_protocol}://{host}/post") 563 { 564 Method = HttpMethod.Post, 565 AllowInsecureRequests = true, 566 }; 567 568 request.AddParameter("testkey1", "testval1"); 569 request.AddParameter("testkey2", "testval2"); 570 571 if (async) 572 Assert.DoesNotThrowAsync(request.PerformAsync); 573 else 574 Assert.DoesNotThrow(request.Perform); 575 576 var responseObject = request.ResponseObject; 577 578 Assert.IsTrue(request.Completed); 579 Assert.IsFalse(request.Aborted); 580 581 Assert.IsTrue(responseObject.Form != null); 582 Assert.IsTrue(responseObject.Form.Count == 2); 583 584 Assert.IsTrue(responseObject.Headers.ContentLength > 0); 585 586 Assert.IsTrue(responseObject.Form.ContainsKey("testkey1")); 587 Assert.IsTrue(responseObject.Form["testkey1"] == "testval1"); 588 589 Assert.IsTrue(responseObject.Form.ContainsKey("testkey2")); 590 Assert.IsTrue(responseObject.Form["testkey2"] == "testval2"); 591 592 Assert.IsTrue(responseObject.Headers.ContentType.StartsWith("multipart/form-data; boundary=", StringComparison.Ordinal)); 593 } 594 595 [Test, Retry(5)] 596 public void TestPostWithJsonRequest([Values(true, false)] bool async) 597 { 598 var request = new JsonWebRequest<HttpBinPostResponse>($"{default_protocol}://{host}/post") 599 { 600 Method = HttpMethod.Post, 601 AllowInsecureRequests = true, 602 }; 603 604 var testObject = new TestObject(); 605 request.AddRaw(JsonConvert.SerializeObject(testObject)); 606 607 if (async) 608 Assert.DoesNotThrowAsync(request.PerformAsync); 609 else 610 Assert.DoesNotThrow(request.Perform); 611 612 var responseObject = request.ResponseObject; 613 614 Assert.IsTrue(request.Completed); 615 Assert.IsFalse(request.Aborted); 616 617 Assert.IsTrue(responseObject.Headers.ContentLength > 0); 618 Assert.IsTrue(responseObject.Json != null); 619 Assert.AreEqual(testObject.TestString, responseObject.Json.TestString); 620 621 Assert.IsTrue(responseObject.Headers.ContentType == null); 622 } 623 624 [Test, Retry(5)] 625 public void TestNoContentPost([Values(true, false)] bool async) 626 { 627 var request = new WebRequest($"{default_protocol}://{host}/anything") 628 { 629 Method = HttpMethod.Post, 630 AllowInsecureRequests = true, 631 }; 632 633 if (async) 634 Assert.DoesNotThrowAsync(request.PerformAsync); 635 else 636 Assert.DoesNotThrow(request.Perform); 637 638 var responseJson = JsonConvert.DeserializeObject<HttpBinPostResponse>(request.GetResponseString()); 639 640 Assert.IsTrue(request.Completed); 641 Assert.IsFalse(request.Aborted); 642 Assert.AreEqual(0, responseJson?.Headers.ContentLength); 643 } 644 645 [Test, Retry(5)] 646 public void TestPutWithQueryAndFormParams() 647 { 648 const string test_key_1 = "param1"; 649 const string test_val_1 = "in query! "; 650 651 const string test_key_2 = "param2"; 652 const string test_val_2 = "in form!"; 653 654 const string test_key_3 = "param3"; 655 const string test_val_3 = "in form by default!"; 656 657 var request = new JsonWebRequest<HttpBinPutResponse>($"{default_protocol}://{host}/put") 658 { 659 Method = HttpMethod.Put, 660 AllowInsecureRequests = true, 661 }; 662 663 request.AddParameter(test_key_1, test_val_1, RequestParameterType.Query); 664 request.AddParameter(test_key_2, test_val_2, RequestParameterType.Form); 665 request.AddParameter(test_key_3, test_val_3); 666 667 Assert.DoesNotThrow(request.Perform); 668 669 Assert.IsTrue(request.Completed); 670 Assert.IsFalse(request.Aborted); 671 672 var response = request.ResponseObject; 673 674 Assert.NotNull(response.Arguments); 675 Assert.True(response.Arguments.ContainsKey(test_key_1)); 676 Assert.AreEqual(test_val_1, response.Arguments[test_key_1]); 677 678 Assert.NotNull(response.Form); 679 Assert.True(response.Form.ContainsKey(test_key_2)); 680 Assert.AreEqual(test_val_2, response.Form[test_key_2]); 681 682 Assert.NotNull(response.Form); 683 Assert.True(response.Form.ContainsKey(test_key_3)); 684 Assert.AreEqual(test_val_3, response.Form[test_key_3]); 685 } 686 687 [Test] 688 public void TestFormParamsNotSupportedForGet() 689 { 690 var request = new JsonWebRequest<HttpBinPutResponse>($"{default_protocol}://{host}/get") 691 { 692 Method = HttpMethod.Get, 693 AllowInsecureRequests = true, 694 }; 695 696 Assert.Throws<ArgumentException>(() => request.AddParameter("cannot", "work", RequestParameterType.Form)); 697 } 698 699 [Test, Retry(5)] 700 public void TestGetBinaryData([Values(true, false)] bool async, [Values(true, false)] bool chunked) 701 { 702 const int bytes_count = 65536; 703 const int chunk_size = 1024; 704 705 string endpoint = chunked ? "stream-bytes" : "bytes"; 706 707 WebRequest request = new WebRequest($"{default_protocol}://{host}/{endpoint}/{bytes_count}") 708 { 709 Method = HttpMethod.Get, 710 AllowInsecureRequests = true, 711 }; 712 if (chunked) 713 request.AddParameter("chunk_size", chunk_size.ToString()); 714 715 if (async) 716 Assert.DoesNotThrowAsync(request.PerformAsync); 717 else 718 Assert.DoesNotThrow(request.Perform); 719 720 Assert.IsTrue(request.Completed); 721 Assert.IsFalse(request.Aborted); 722 723 Assert.AreEqual(bytes_count, request.ResponseStream.Length); 724 } 725 726 [Serializable] 727 private class HttpBinGetResponse 728 { 729 [JsonProperty("args")] 730 public Dictionary<string, string> Arguments { get; set; } 731 732 [JsonProperty("headers")] 733 public HttpBinHeaders Headers { get; set; } 734 735 [JsonProperty("url")] 736 public string Url { get; set; } 737 } 738 739 [Serializable] 740 private class HttpBinPostResponse 741 { 742 [JsonProperty("data")] 743 public string Data { get; set; } 744 745 [JsonProperty("form")] 746 public IDictionary<string, string> Form { get; set; } 747 748 [JsonProperty("headers")] 749 public HttpBinHeaders Headers { get; set; } 750 751 [JsonProperty("json")] 752 public TestObject Json { get; set; } 753 } 754 755 [Serializable] 756 private class HttpBinPutResponse 757 { 758 [JsonProperty("args")] 759 public Dictionary<string, string> Arguments { get; set; } 760 761 [JsonProperty("form")] 762 public Dictionary<string, string> Form { get; set; } 763 } 764 765 [Serializable] 766 public class HttpBinHeaders 767 { 768 [JsonProperty("Content-Length")] 769 public int ContentLength { get; set; } 770 771 [JsonProperty("Content-Type")] 772 public string ContentType { get; set; } 773 774 [JsonProperty("User-Agent")] 775 public string UserAgent { get; set; } 776 } 777 778 [Serializable] 779 public class TestObject 780 { 781 public string TestString = "readable"; 782 } 783 784 private class CustomUserAgentWebRequest : JsonWebRequest<HttpBinGetResponse> 785 { 786 public CustomUserAgentWebRequest(string url) 787 : base(url) 788 { 789 } 790 791 protected override string UserAgent => "custom-ua"; 792 } 793 794 private class DelayedWebRequest : WebRequest 795 { 796 public Action CompleteInvoked; 797 798 private int delay; 799 800 public int Delay 801 { 802 get => delay; 803 set 804 { 805 delay = value; 806 Url = $"{default_protocol}://{host}/delay/{delay}"; 807 } 808 } 809 810 public DelayedWebRequest() 811 : base($"{default_protocol}://{host}/delay/0") 812 { 813 } 814 815 protected override void Complete(Exception e = null) 816 { 817 CompleteInvoked?.Invoke(); 818 base.Complete(e); 819 } 820 } 821 } 822}