public void ToString_UseDifferentRanges_AllSerializedCorrectly() { ContentRangeHeaderValue range = new ContentRangeHeaderValue(1, 2, 3); range.Unit = "myunit"; Assert.Equal("myunit 1-2/3", range.ToString()); // "Range with all fields set" range = new ContentRangeHeaderValue(123456789012345678, 123456789012345679); Assert.Equal("bytes 123456789012345678-123456789012345679/*", range.ToString()); // "Only range, no length" range = new ContentRangeHeaderValue(150); Assert.Equal("bytes */150", range.ToString()); // "Only length, no range" }
/// <summary> /// Run a simple HTTP server that listens for a few test URIs. /// The server exits when a client requests "/Quit". /// </summary> /// <param name="prefix">Prefix at which the server should listen</param> private async Task RunTestServer(string prefix) { using (var httpListener = new HttpListener()) { httpListener.Prefixes.Add(prefix); httpListener.Start(); while (httpListener.IsListening) { var context = await httpListener.GetContextAsync(); var requestUri = context.Request.Url; var response = context.Response; if (requestUri.AbsolutePath.EndsWith("/Quit")) { // Shut down the HTTP server. response.Close(); httpListener.Stop(); continue; } // All downloader requests should include ?alt=media. Assert.AreEqual("media", context.Request.QueryString["alt"]); response.ContentType = "text/plain"; response.SendChunked = true; // Avoid having to set Content-Length. Stream outStream = new MemoryStream(); if (requestUri.AbsolutePath.EndsWith("/EchoUrl")) { // Return the URL that we saw. byte[] uriBytes = Encoding.UTF8.GetBytes(requestUri.AbsoluteUri); outStream.Write(uriBytes, 0, uriBytes.Length); } else if (requestUri.AbsolutePath.EndsWith("/BadRequestJson")) { // Return 400 with a JSON-encoded error. var apiResponse = new StandardResponse<object> { Error = BadRequestError }; var apiResponseText = new NewtonsoftJsonSerializer().Serialize(apiResponse); byte[] apiResponseBytes = Encoding.UTF8.GetBytes(apiResponseText); response.StatusCode = (int)HttpStatusCode.BadRequest; outStream.Write(apiResponseBytes, 0, apiResponseBytes.Length); } else if (requestUri.AbsolutePath.EndsWith("/NotFoundPlainText")) { // Return 404 with a plaintext error. response.StatusCode = (int)HttpStatusCode.NotFound; byte[] errorBytes = Encoding.UTF8.GetBytes(NotFoundError); outStream.Write(errorBytes, 0, errorBytes.Length); } else if (requestUri.AbsolutePath.EndsWith("/GzipContent")) { // Return gzip-compressed content. using (var gzipStream = new GZipStream(outStream, CompressionMode.Compress, true)) { gzipStream.Write(MediaContent, 0, MediaContent.Length); } response.AddHeader("Content-Encoding", "gzip"); } else { // Return plaintext content. outStream.Write(MediaContent, 0, MediaContent.Length); } outStream.Position = 0; // Provide rudimentary, non-robust support for Range. // MediaDownloader doesn't exercise this code anymore, but it was useful for // testing previous implementations that did. It remains for posterity. string rangeHeader = context.Request.Headers["Range"]; if (rangeHeader != null && response.StatusCode == (int)HttpStatusCode.OK) { var range = RangeHeaderValue.Parse(rangeHeader); var firstRange = range.Ranges.First(); long from = firstRange.From ?? 0; long to = Math.Min(outStream.Length - 1, firstRange.To ?? long.MaxValue); var contentRangeHeader = new ContentRangeHeaderValue(from, to, outStream.Length); response.AddHeader("Content-Range", contentRangeHeader.ToString()); outStream.Position = from; outStream.SetLength(to + 1); } await outStream.CopyToAsync(response.OutputStream); response.Close(); } } }