BaseHandler.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.Kernel;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Collections.Specialized;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.IO.Compression;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Common.Net.Handlers
  15. {
  16. /// <summary>
  17. /// Class BaseHandler
  18. /// </summary>
  19. public abstract class BaseHandler<TKernelType> : IHttpServerHandler
  20. where TKernelType : IKernel
  21. {
  22. /// <summary>
  23. /// Initializes the specified kernel.
  24. /// </summary>
  25. /// <param name="kernel">The kernel.</param>
  26. public void Initialize(IKernel kernel)
  27. {
  28. Kernel = (TKernelType)kernel;
  29. }
  30. /// <summary>
  31. /// Gets or sets the kernel.
  32. /// </summary>
  33. /// <value>The kernel.</value>
  34. protected TKernelType Kernel { get; private set; }
  35. /// <summary>
  36. /// Gets the URL suffix used to determine if this handler can process a request.
  37. /// </summary>
  38. /// <value>The URL suffix.</value>
  39. protected virtual string UrlSuffix
  40. {
  41. get
  42. {
  43. var name = GetType().Name;
  44. const string srch = "Handler";
  45. if (name.EndsWith(srch, StringComparison.OrdinalIgnoreCase))
  46. {
  47. name = name.Substring(0, name.Length - srch.Length);
  48. }
  49. return "api/" + name;
  50. }
  51. }
  52. /// <summary>
  53. /// Handleses the request.
  54. /// </summary>
  55. /// <param name="request">The request.</param>
  56. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  57. public virtual bool HandlesRequest(HttpListenerRequest request)
  58. {
  59. var name = '/' + UrlSuffix.TrimStart('/');
  60. var url = Kernel.WebApplicationName + name;
  61. return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase);
  62. }
  63. /// <summary>
  64. /// Gets or sets the compressed stream.
  65. /// </summary>
  66. /// <value>The compressed stream.</value>
  67. private Stream CompressedStream { get; set; }
  68. /// <summary>
  69. /// Gets a value indicating whether [use chunked encoding].
  70. /// </summary>
  71. /// <value><c>null</c> if [use chunked encoding] contains no value, <c>true</c> if [use chunked encoding]; otherwise, <c>false</c>.</value>
  72. public virtual bool? UseChunkedEncoding
  73. {
  74. get
  75. {
  76. return null;
  77. }
  78. }
  79. /// <summary>
  80. /// The original HttpListenerContext
  81. /// </summary>
  82. /// <value>The HTTP listener context.</value>
  83. protected HttpListenerContext HttpListenerContext { get; set; }
  84. /// <summary>
  85. /// The _query string
  86. /// </summary>
  87. private NameValueCollection _queryString;
  88. /// <summary>
  89. /// The original QueryString
  90. /// </summary>
  91. /// <value>The query string.</value>
  92. public NameValueCollection QueryString
  93. {
  94. get
  95. {
  96. // HttpListenerContext.Request.QueryString is not decoded properly
  97. return _queryString;
  98. }
  99. }
  100. /// <summary>
  101. /// The _requested ranges
  102. /// </summary>
  103. private List<KeyValuePair<long, long?>> _requestedRanges;
  104. /// <summary>
  105. /// Gets the requested ranges.
  106. /// </summary>
  107. /// <value>The requested ranges.</value>
  108. protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
  109. {
  110. get
  111. {
  112. if (_requestedRanges == null)
  113. {
  114. _requestedRanges = new List<KeyValuePair<long, long?>>();
  115. if (IsRangeRequest)
  116. {
  117. // Example: bytes=0-,32-63
  118. var ranges = HttpListenerContext.Request.Headers["Range"].Split('=')[1].Split(',');
  119. foreach (var range in ranges)
  120. {
  121. var vals = range.Split('-');
  122. long start = 0;
  123. long? end = null;
  124. if (!string.IsNullOrEmpty(vals[0]))
  125. {
  126. start = long.Parse(vals[0]);
  127. }
  128. if (!string.IsNullOrEmpty(vals[1]))
  129. {
  130. end = long.Parse(vals[1]);
  131. }
  132. _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
  133. }
  134. }
  135. }
  136. return _requestedRanges;
  137. }
  138. }
  139. /// <summary>
  140. /// Gets a value indicating whether this instance is range request.
  141. /// </summary>
  142. /// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
  143. protected bool IsRangeRequest
  144. {
  145. get
  146. {
  147. return HttpListenerContext.Request.Headers.AllKeys.Contains("Range");
  148. }
  149. }
  150. /// <summary>
  151. /// Gets a value indicating whether [client supports compression].
  152. /// </summary>
  153. /// <value><c>true</c> if [client supports compression]; otherwise, <c>false</c>.</value>
  154. protected bool ClientSupportsCompression
  155. {
  156. get
  157. {
  158. var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
  159. return enc.Equals("*", StringComparison.OrdinalIgnoreCase) ||
  160. enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 ||
  161. enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1;
  162. }
  163. }
  164. /// <summary>
  165. /// Gets the compression method.
  166. /// </summary>
  167. /// <value>The compression method.</value>
  168. private string CompressionMethod
  169. {
  170. get
  171. {
  172. var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
  173. if (enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || enc.Equals("*", StringComparison.OrdinalIgnoreCase))
  174. {
  175. return "deflate";
  176. }
  177. if (enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
  178. {
  179. return "gzip";
  180. }
  181. return null;
  182. }
  183. }
  184. /// <summary>
  185. /// Processes the request.
  186. /// </summary>
  187. /// <param name="ctx">The CTX.</param>
  188. /// <returns>Task.</returns>
  189. public virtual async Task ProcessRequest(HttpListenerContext ctx)
  190. {
  191. HttpListenerContext = ctx;
  192. ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
  193. ctx.Response.KeepAlive = true;
  194. try
  195. {
  196. await ProcessRequestInternal(ctx).ConfigureAwait(false);
  197. }
  198. catch (InvalidOperationException ex)
  199. {
  200. HandleException(ctx.Response, ex, 422);
  201. throw;
  202. }
  203. catch (ResourceNotFoundException ex)
  204. {
  205. HandleException(ctx.Response, ex, 404);
  206. throw;
  207. }
  208. catch (FileNotFoundException ex)
  209. {
  210. HandleException(ctx.Response, ex, 404);
  211. throw;
  212. }
  213. catch (DirectoryNotFoundException ex)
  214. {
  215. HandleException(ctx.Response, ex, 404);
  216. throw;
  217. }
  218. catch (UnauthorizedAccessException ex)
  219. {
  220. HandleException(ctx.Response, ex, 401);
  221. throw;
  222. }
  223. catch (ArgumentException ex)
  224. {
  225. HandleException(ctx.Response, ex, 400);
  226. throw;
  227. }
  228. catch (Exception ex)
  229. {
  230. HandleException(ctx.Response, ex, 500);
  231. throw;
  232. }
  233. finally
  234. {
  235. DisposeResponseStream();
  236. }
  237. }
  238. /// <summary>
  239. /// Appends the error message.
  240. /// </summary>
  241. /// <param name="response">The response.</param>
  242. /// <param name="ex">The ex.</param>
  243. /// <param name="statusCode">The status code.</param>
  244. private void HandleException(HttpListenerResponse response, Exception ex, int statusCode)
  245. {
  246. response.StatusCode = statusCode;
  247. response.Headers.Add("Status", statusCode.ToString(new CultureInfo("en-US")));
  248. response.Headers.Remove("Age");
  249. response.Headers.Remove("Expires");
  250. response.Headers.Remove("Cache-Control");
  251. response.Headers.Remove("Etag");
  252. response.Headers.Remove("Last-Modified");
  253. response.ContentType = "text/plain";
  254. //Logger.ErrorException("Error processing request", ex);
  255. if (!string.IsNullOrEmpty(ex.Message))
  256. {
  257. response.AddHeader("X-Application-Error-Code", ex.Message);
  258. }
  259. var bytes = Encoding.UTF8.GetBytes(ex.Message);
  260. var stream = CompressedStream ?? response.OutputStream;
  261. // This could fail, but try to add the stack trace as the body content
  262. try
  263. {
  264. stream.Write(bytes, 0, bytes.Length);
  265. }
  266. catch (Exception ex1)
  267. {
  268. //Logger.ErrorException("Error dumping stack trace", ex1);
  269. }
  270. }
  271. /// <summary>
  272. /// Processes the request internal.
  273. /// </summary>
  274. /// <param name="ctx">The CTX.</param>
  275. /// <returns>Task.</returns>
  276. private async Task ProcessRequestInternal(HttpListenerContext ctx)
  277. {
  278. var responseInfo = await GetResponseInfo().ConfigureAwait(false);
  279. // Let the client know if byte range requests are supported or not
  280. if (responseInfo.SupportsByteRangeRequests)
  281. {
  282. ctx.Response.Headers["Accept-Ranges"] = "bytes";
  283. }
  284. else if (!responseInfo.SupportsByteRangeRequests)
  285. {
  286. ctx.Response.Headers["Accept-Ranges"] = "none";
  287. }
  288. if (responseInfo.IsResponseValid && responseInfo.SupportsByteRangeRequests && IsRangeRequest)
  289. {
  290. // Set the initial status code
  291. // When serving a range request, we need to return status code 206 to indicate a partial response body
  292. responseInfo.StatusCode = 206;
  293. }
  294. ctx.Response.ContentType = responseInfo.ContentType;
  295. if (responseInfo.Etag.HasValue)
  296. {
  297. ctx.Response.Headers["ETag"] = responseInfo.Etag.Value.ToString("N");
  298. }
  299. var isCacheValid = true;
  300. // Validate If-Modified-Since
  301. if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
  302. {
  303. DateTime ifModifiedSince;
  304. if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince))
  305. {
  306. isCacheValid = IsCacheValid(ifModifiedSince.ToUniversalTime(), responseInfo.CacheDuration,
  307. responseInfo.DateLastModified);
  308. }
  309. }
  310. // Validate If-None-Match
  311. if (isCacheValid &&
  312. (responseInfo.Etag.HasValue || !string.IsNullOrEmpty(ctx.Request.Headers["If-None-Match"])))
  313. {
  314. Guid ifNoneMatch;
  315. if (Guid.TryParse(ctx.Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch))
  316. {
  317. if (responseInfo.Etag.HasValue && responseInfo.Etag.Value == ifNoneMatch)
  318. {
  319. responseInfo.StatusCode = 304;
  320. }
  321. }
  322. }
  323. LogResponse(ctx, responseInfo);
  324. if (responseInfo.IsResponseValid)
  325. {
  326. await OnProcessingRequest(responseInfo).ConfigureAwait(false);
  327. }
  328. if (responseInfo.IsResponseValid)
  329. {
  330. await ProcessUncachedRequest(ctx, responseInfo).ConfigureAwait(false);
  331. }
  332. else
  333. {
  334. if (responseInfo.StatusCode == 304)
  335. {
  336. AddAgeHeader(ctx.Response, responseInfo);
  337. AddExpiresHeader(ctx.Response, responseInfo);
  338. }
  339. ctx.Response.StatusCode = responseInfo.StatusCode;
  340. ctx.Response.SendChunked = false;
  341. }
  342. }
  343. /// <summary>
  344. /// The _null task result
  345. /// </summary>
  346. private readonly Task<bool> _nullTaskResult = Task.FromResult(true);
  347. /// <summary>
  348. /// Called when [processing request].
  349. /// </summary>
  350. /// <param name="responseInfo">The response info.</param>
  351. /// <returns>Task.</returns>
  352. protected virtual Task OnProcessingRequest(ResponseInfo responseInfo)
  353. {
  354. return _nullTaskResult;
  355. }
  356. /// <summary>
  357. /// Logs the response.
  358. /// </summary>
  359. /// <param name="ctx">The CTX.</param>
  360. /// <param name="responseInfo">The response info.</param>
  361. private void LogResponse(HttpListenerContext ctx, ResponseInfo responseInfo)
  362. {
  363. // Don't log normal 200's
  364. if (responseInfo.StatusCode == 200)
  365. {
  366. return;
  367. }
  368. var log = new StringBuilder();
  369. log.AppendLine(string.Format("Url: {0}", ctx.Request.Url));
  370. log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
  371. var msg = "Http Response Sent (" + responseInfo.StatusCode + ") to " + ctx.Request.RemoteEndPoint;
  372. if (Kernel.Configuration.EnableHttpLevelLogging)
  373. {
  374. //Logger.LogMultiline(msg, LogSeverity.Debug, log);
  375. }
  376. }
  377. /// <summary>
  378. /// Processes the uncached request.
  379. /// </summary>
  380. /// <param name="ctx">The CTX.</param>
  381. /// <param name="responseInfo">The response info.</param>
  382. /// <returns>Task.</returns>
  383. private async Task ProcessUncachedRequest(HttpListenerContext ctx, ResponseInfo responseInfo)
  384. {
  385. var totalContentLength = GetTotalContentLength(responseInfo);
  386. // By default, use chunked encoding if we don't know the content length
  387. var useChunkedEncoding = UseChunkedEncoding == null ? (totalContentLength == null) : UseChunkedEncoding.Value;
  388. // Don't force this to true. HttpListener will default it to true if supported by the client.
  389. if (!useChunkedEncoding)
  390. {
  391. ctx.Response.SendChunked = false;
  392. }
  393. // Set the content length, if we know it
  394. if (totalContentLength.HasValue)
  395. {
  396. ctx.Response.ContentLength64 = totalContentLength.Value;
  397. }
  398. var compressResponse = responseInfo.CompressResponse && ClientSupportsCompression;
  399. // Add the compression header
  400. if (compressResponse)
  401. {
  402. ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
  403. ctx.Response.AddHeader("Vary", "Accept-Encoding");
  404. }
  405. // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
  406. // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
  407. if (responseInfo.DateLastModified.HasValue && (!responseInfo.Etag.HasValue || responseInfo.CacheDuration.Ticks > 0))
  408. {
  409. ctx.Response.Headers[HttpResponseHeader.LastModified] = responseInfo.DateLastModified.Value.ToString("r");
  410. AddAgeHeader(ctx.Response, responseInfo);
  411. }
  412. // Add caching headers
  413. ConfigureCaching(ctx.Response, responseInfo);
  414. // Set the status code
  415. ctx.Response.StatusCode = responseInfo.StatusCode;
  416. if (responseInfo.IsResponseValid)
  417. {
  418. // Finally, write the response data
  419. var outputStream = ctx.Response.OutputStream;
  420. if (compressResponse)
  421. {
  422. if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
  423. {
  424. CompressedStream = new DeflateStream(outputStream, CompressionLevel.Fastest, true);
  425. }
  426. else
  427. {
  428. CompressedStream = new GZipStream(outputStream, CompressionLevel.Fastest, true);
  429. }
  430. outputStream = CompressedStream;
  431. }
  432. await WriteResponseToOutputStream(outputStream, responseInfo, totalContentLength).ConfigureAwait(false);
  433. }
  434. else
  435. {
  436. ctx.Response.SendChunked = false;
  437. }
  438. }
  439. /// <summary>
  440. /// Configures the caching.
  441. /// </summary>
  442. /// <param name="response">The response.</param>
  443. /// <param name="responseInfo">The response info.</param>
  444. private void ConfigureCaching(HttpListenerResponse response, ResponseInfo responseInfo)
  445. {
  446. if (responseInfo.CacheDuration.Ticks > 0)
  447. {
  448. response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(responseInfo.CacheDuration.TotalSeconds);
  449. }
  450. else if (responseInfo.Etag.HasValue)
  451. {
  452. response.Headers[HttpResponseHeader.CacheControl] = "public";
  453. }
  454. else
  455. {
  456. response.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, must-revalidate";
  457. response.Headers[HttpResponseHeader.Pragma] = "no-cache, no-store, must-revalidate";
  458. }
  459. AddExpiresHeader(response, responseInfo);
  460. }
  461. /// <summary>
  462. /// Adds the expires header.
  463. /// </summary>
  464. /// <param name="response">The response.</param>
  465. /// <param name="responseInfo">The response info.</param>
  466. private void AddExpiresHeader(HttpListenerResponse response, ResponseInfo responseInfo)
  467. {
  468. if (responseInfo.CacheDuration.Ticks > 0)
  469. {
  470. response.Headers[HttpResponseHeader.Expires] = DateTime.UtcNow.Add(responseInfo.CacheDuration).ToString("r");
  471. }
  472. else if (!responseInfo.Etag.HasValue)
  473. {
  474. response.Headers[HttpResponseHeader.Expires] = "-1";
  475. }
  476. }
  477. /// <summary>
  478. /// Adds the age header.
  479. /// </summary>
  480. /// <param name="response">The response.</param>
  481. /// <param name="responseInfo">The response info.</param>
  482. private void AddAgeHeader(HttpListenerResponse response, ResponseInfo responseInfo)
  483. {
  484. if (responseInfo.DateLastModified.HasValue)
  485. {
  486. response.Headers[HttpResponseHeader.Age] = Convert.ToInt32((DateTime.UtcNow - responseInfo.DateLastModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
  487. }
  488. }
  489. /// <summary>
  490. /// Writes the response to output stream.
  491. /// </summary>
  492. /// <param name="stream">The stream.</param>
  493. /// <param name="responseInfo">The response info.</param>
  494. /// <param name="contentLength">Length of the content.</param>
  495. /// <returns>Task.</returns>
  496. protected abstract Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? contentLength);
  497. /// <summary>
  498. /// Disposes the response stream.
  499. /// </summary>
  500. protected virtual void DisposeResponseStream()
  501. {
  502. if (CompressedStream != null)
  503. {
  504. try
  505. {
  506. CompressedStream.Dispose();
  507. }
  508. catch (Exception ex)
  509. {
  510. //Logger.ErrorException("Error disposing compressed stream", ex);
  511. }
  512. }
  513. try
  514. {
  515. //HttpListenerContext.Response.OutputStream.Dispose();
  516. HttpListenerContext.Response.Close();
  517. }
  518. catch (Exception ex)
  519. {
  520. //Logger.ErrorException("Error disposing response", ex);
  521. }
  522. }
  523. /// <summary>
  524. /// Determines whether [is cache valid] [the specified if modified since].
  525. /// </summary>
  526. /// <param name="ifModifiedSince">If modified since.</param>
  527. /// <param name="cacheDuration">Duration of the cache.</param>
  528. /// <param name="dateModified">The date modified.</param>
  529. /// <returns><c>true</c> if [is cache valid] [the specified if modified since]; otherwise, <c>false</c>.</returns>
  530. private bool IsCacheValid(DateTime ifModifiedSince, TimeSpan cacheDuration, DateTime? dateModified)
  531. {
  532. if (dateModified.HasValue)
  533. {
  534. DateTime lastModified = NormalizeDateForComparison(dateModified.Value);
  535. ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
  536. return lastModified <= ifModifiedSince;
  537. }
  538. DateTime cacheExpirationDate = ifModifiedSince.Add(cacheDuration);
  539. if (DateTime.UtcNow < cacheExpirationDate)
  540. {
  541. return true;
  542. }
  543. return false;
  544. }
  545. /// <summary>
  546. /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
  547. /// </summary>
  548. /// <param name="date">The date.</param>
  549. /// <returns>DateTime.</returns>
  550. private DateTime NormalizeDateForComparison(DateTime date)
  551. {
  552. return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
  553. }
  554. /// <summary>
  555. /// Gets the total length of the content.
  556. /// </summary>
  557. /// <param name="responseInfo">The response info.</param>
  558. /// <returns>System.Nullable{System.Int64}.</returns>
  559. protected virtual long? GetTotalContentLength(ResponseInfo responseInfo)
  560. {
  561. return null;
  562. }
  563. /// <summary>
  564. /// Gets the response info.
  565. /// </summary>
  566. /// <returns>Task{ResponseInfo}.</returns>
  567. protected abstract Task<ResponseInfo> GetResponseInfo();
  568. /// <summary>
  569. /// Gets a bool query string param.
  570. /// </summary>
  571. /// <param name="name">The name.</param>
  572. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  573. protected bool GetBoolQueryStringParam(string name)
  574. {
  575. var val = QueryString[name] ?? string.Empty;
  576. return val.Equals("1", StringComparison.OrdinalIgnoreCase) || val.Equals("true", StringComparison.OrdinalIgnoreCase);
  577. }
  578. /// <summary>
  579. /// The _form values
  580. /// </summary>
  581. private Hashtable _formValues;
  582. /// <summary>
  583. /// Gets a value from form POST data
  584. /// </summary>
  585. /// <param name="name">The name.</param>
  586. /// <returns>Task{System.String}.</returns>
  587. protected async Task<string> GetFormValue(string name)
  588. {
  589. if (_formValues == null)
  590. {
  591. _formValues = await GetFormValues(HttpListenerContext.Request).ConfigureAwait(false);
  592. }
  593. if (_formValues.ContainsKey(name))
  594. {
  595. return _formValues[name].ToString();
  596. }
  597. return null;
  598. }
  599. /// <summary>
  600. /// Extracts form POST data from a request
  601. /// </summary>
  602. /// <param name="request">The request.</param>
  603. /// <returns>Task{Hashtable}.</returns>
  604. private async Task<Hashtable> GetFormValues(HttpListenerRequest request)
  605. {
  606. var formVars = new Hashtable();
  607. if (request.HasEntityBody)
  608. {
  609. if (request.ContentType.IndexOf("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) != -1)
  610. {
  611. using (var requestBody = request.InputStream)
  612. {
  613. using (var reader = new StreamReader(requestBody, request.ContentEncoding))
  614. {
  615. var s = await reader.ReadToEndAsync().ConfigureAwait(false);
  616. var pairs = s.Split('&');
  617. foreach (var pair in pairs)
  618. {
  619. var index = pair.IndexOf('=');
  620. if (index != -1)
  621. {
  622. var name = pair.Substring(0, index);
  623. var value = pair.Substring(index + 1);
  624. formVars.Add(name, value);
  625. }
  626. }
  627. }
  628. }
  629. }
  630. }
  631. return formVars;
  632. }
  633. }
  634. /// <summary>
  635. /// Class ResponseInfo
  636. /// </summary>
  637. public class ResponseInfo
  638. {
  639. /// <summary>
  640. /// Gets or sets the type of the content.
  641. /// </summary>
  642. /// <value>The type of the content.</value>
  643. public string ContentType { get; set; }
  644. /// <summary>
  645. /// Gets or sets the etag.
  646. /// </summary>
  647. /// <value>The etag.</value>
  648. public Guid? Etag { get; set; }
  649. /// <summary>
  650. /// Gets or sets the date last modified.
  651. /// </summary>
  652. /// <value>The date last modified.</value>
  653. public DateTime? DateLastModified { get; set; }
  654. /// <summary>
  655. /// Gets or sets the duration of the cache.
  656. /// </summary>
  657. /// <value>The duration of the cache.</value>
  658. public TimeSpan CacheDuration { get; set; }
  659. /// <summary>
  660. /// Gets or sets a value indicating whether [compress response].
  661. /// </summary>
  662. /// <value><c>true</c> if [compress response]; otherwise, <c>false</c>.</value>
  663. public bool CompressResponse { get; set; }
  664. /// <summary>
  665. /// Gets or sets the status code.
  666. /// </summary>
  667. /// <value>The status code.</value>
  668. public int StatusCode { get; set; }
  669. /// <summary>
  670. /// Gets or sets a value indicating whether [supports byte range requests].
  671. /// </summary>
  672. /// <value><c>true</c> if [supports byte range requests]; otherwise, <c>false</c>.</value>
  673. public bool SupportsByteRangeRequests { get; set; }
  674. /// <summary>
  675. /// Initializes a new instance of the <see cref="ResponseInfo" /> class.
  676. /// </summary>
  677. public ResponseInfo()
  678. {
  679. CacheDuration = TimeSpan.FromTicks(0);
  680. CompressResponse = true;
  681. StatusCode = 200;
  682. }
  683. /// <summary>
  684. /// Gets a value indicating whether this instance is response valid.
  685. /// </summary>
  686. /// <value><c>true</c> if this instance is response valid; otherwise, <c>false</c>.</value>
  687. public bool IsResponseValid
  688. {
  689. get
  690. {
  691. return StatusCode >= 200 && StatusCode < 300;
  692. }
  693. }
  694. }
  695. }