HttpResponse.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. using System;
  2. using System.Collections.Specialized;
  3. using System.IO;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace OSHttpServer
  9. {
  10. public class HttpResponse : IHttpResponse
  11. {
  12. private const string DefaultContentType = "text/html;charset=UTF-8";
  13. private readonly IHttpClientContext m_context;
  14. private readonly ResponseCookies m_cookies = new ResponseCookies();
  15. private readonly NameValueCollection m_headers = new NameValueCollection();
  16. private string m_httpVersion;
  17. private Stream m_body;
  18. private long m_contentLength;
  19. private string m_contentType;
  20. private Encoding m_encoding = Encoding.UTF8;
  21. private int m_keepAlive = 60;
  22. public uint requestID { get; private set; }
  23. public byte[] RawBuffer { get; set; }
  24. public int RawBufferStart { get; set; }
  25. public int RawBufferLen { get; set; }
  26. internal byte[] m_headerBytes = null;
  27. /// <summary>
  28. /// Initializes a new instance of the <see cref="IHttpResponse"/> class.
  29. /// </summary>
  30. /// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
  31. /// <param name="request">Contains information of what the client want to receive.</param>
  32. /// <exception cref="ArgumentException"><see cref="IHttpRequest.HttpVersion"/> cannot be empty.</exception>
  33. public HttpResponse(IHttpClientContext context, IHttpRequest request)
  34. {
  35. m_httpVersion = request.HttpVersion;
  36. if (string.IsNullOrEmpty(m_httpVersion))
  37. m_httpVersion = "HTTP/1.0";
  38. Status = HttpStatusCode.OK;
  39. m_context = context;
  40. m_Connetion = request.Connection;
  41. requestID = request.ID;
  42. RawBufferStart = -1;
  43. RawBufferLen = -1;
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the <see cref="IHttpResponse"/> class.
  47. /// </summary>
  48. /// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
  49. /// <param name="httpVersion">Version of HTTP protocol that the client uses.</param>
  50. /// <param name="connectionType">Type of HTTP connection used.</param>
  51. internal HttpResponse(IHttpClientContext context, string httpVersion, ConnectionType connectionType)
  52. {
  53. Status = HttpStatusCode.OK;
  54. m_context = context;
  55. m_httpVersion = httpVersion;
  56. m_Connetion = connectionType;
  57. }
  58. private ConnectionType m_Connetion;
  59. public ConnectionType Connection
  60. {
  61. get { return m_Connetion; }
  62. set { return; }
  63. }
  64. private int m_priority = 0;
  65. public int Priority
  66. {
  67. get { return m_priority;}
  68. set { m_priority = (value > 0 && m_priority < 3)? value : 0;}
  69. }
  70. #region IHttpResponse Members
  71. /// <summary>
  72. /// The body stream is used to cache the body contents
  73. /// before sending everything to the client. It's the simplest
  74. /// way to serve documents.
  75. /// </summary>
  76. public Stream Body
  77. {
  78. get
  79. {
  80. if(m_body == null)
  81. m_body = new MemoryStream();
  82. return m_body;
  83. }
  84. set { m_body = value; }
  85. }
  86. /// <summary>
  87. /// The chunked encoding modifies the body of a message in order to
  88. /// transfer it as a series of chunks, each with its own size indicator,
  89. /// followed by an OPTIONAL trailer containing entity-header fields. This
  90. /// allows dynamically produced content to be transferred along with the
  91. /// information necessary for the recipient to verify that it has
  92. /// received the full message.
  93. /// </summary>
  94. public bool Chunked { get; set; }
  95. /// <summary>
  96. /// Defines the version of the HTTP Response for applications where it's required
  97. /// for this to be forced.
  98. /// </summary>
  99. public string ProtocolVersion
  100. {
  101. get { return m_httpVersion; }
  102. set { m_httpVersion = value; }
  103. }
  104. /// <summary>
  105. /// Encoding to use when sending stuff to the client.
  106. /// </summary>
  107. /// <remarks>Default is UTF8</remarks>
  108. public Encoding Encoding
  109. {
  110. get { return m_encoding; }
  111. set { m_encoding = value; }
  112. }
  113. /// <summary>
  114. /// Number of seconds to keep connection alive
  115. /// </summary>
  116. /// <remarks>Only used if Connection property is set to <see cref="ConnectionType.KeepAlive"/>.</remarks>
  117. public int KeepAlive
  118. {
  119. get { return m_keepAlive; }
  120. set
  121. {
  122. if (value > 400)
  123. m_keepAlive = 400;
  124. else if (value <= 0)
  125. m_keepAlive = 0;
  126. else
  127. m_keepAlive = value;
  128. }
  129. }
  130. /// <summary>
  131. /// Status code that is sent to the client.
  132. /// </summary>
  133. /// <remarks>Default is <see cref="HttpStatusCode.OK"/></remarks>
  134. public HttpStatusCode Status { get; set; }
  135. /// <summary>
  136. /// Information about why a specific status code was used.
  137. /// </summary>
  138. public string Reason { get; set; }
  139. /// <summary>
  140. /// Size of the body. MUST be specified before sending the header,
  141. /// unless property Chunked is set to true.
  142. /// </summary>
  143. public long ContentLength
  144. {
  145. get { return m_contentLength; }
  146. set { m_contentLength = value; }
  147. }
  148. /// <summary>
  149. /// Kind of content in the body
  150. /// </summary>
  151. /// <remarks>Default type is "text/html"</remarks>
  152. public string ContentType
  153. {
  154. get { return m_contentType; }
  155. set { m_contentType = value; }
  156. }
  157. /// <summary>
  158. /// Headers have been sent to the client-
  159. /// </summary>
  160. /// <remarks>You can not send any additional headers if they have already been sent.</remarks>
  161. public bool HeadersSent { get; private set; }
  162. /// <summary>
  163. /// The whole response have been sent.
  164. /// </summary>
  165. public bool Sent { get; private set; }
  166. /// <summary>
  167. /// Cookies that should be created/changed.
  168. /// </summary>
  169. public ResponseCookies Cookies
  170. {
  171. get { return m_cookies; }
  172. }
  173. /// <summary>
  174. /// Add another header to the document.
  175. /// </summary>
  176. /// <param name="name">Name of the header, case sensitive, use lower cases.</param>
  177. /// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
  178. /// <exception cref="InvalidOperationException">If headers already been sent.</exception>
  179. /// <exception cref="ArgumentException">If value conditions have not been met.</exception>
  180. /// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
  181. public void AddHeader(string name, string value)
  182. {
  183. if (HeadersSent)
  184. throw new InvalidOperationException("Headers have already been sent.");
  185. for (int i = 1; i < value.Length; ++i)
  186. {
  187. if (value[i] == '\r' && !char.IsWhiteSpace(value[i - 1]))
  188. throw new ArgumentException("New line in value do not start with a white space.");
  189. if (value[i] == '\n' && value[i - 1] != '\r')
  190. throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
  191. }
  192. m_headers[name] = value;
  193. }
  194. /// <summary>
  195. /// Send headers and body to the browser.
  196. /// </summary>
  197. /// <exception cref="InvalidOperationException">If content have already been sent.</exception>
  198. public void SendOri()
  199. {
  200. if (Sent)
  201. throw new InvalidOperationException("Everything have already been sent.");
  202. m_context.ReqResponseAboutToSend(requestID);
  203. if (m_context.MAXRequests == 0 || m_keepAlive == 0)
  204. {
  205. Connection = ConnectionType.Close;
  206. m_context.TimeoutKeepAlive = 0;
  207. }
  208. else
  209. {
  210. if (m_keepAlive > 0)
  211. m_context.TimeoutKeepAlive = m_keepAlive * 1000;
  212. }
  213. if (!HeadersSent)
  214. {
  215. if (!SendHeaders())
  216. {
  217. m_body.Dispose();
  218. Sent = true;
  219. return;
  220. }
  221. }
  222. if(RawBuffer != null)
  223. {
  224. if(RawBufferStart >= 0 && RawBufferLen > 0)
  225. {
  226. if (RawBufferStart > RawBuffer.Length)
  227. RawBufferStart = 0;
  228. if (RawBufferLen + RawBufferStart > RawBuffer.Length)
  229. RawBufferLen = RawBuffer.Length - RawBufferStart;
  230. /*
  231. int curlen;
  232. while(RawBufferLen > 0)
  233. {
  234. curlen = RawBufferLen;
  235. if(curlen > 8192)
  236. curlen = 8192;
  237. if (!_context.Send(RawBuffer, RawBufferStart, curlen))
  238. {
  239. RawBuffer = null;
  240. RawBufferStart = -1;
  241. RawBufferLen = -1;
  242. Body.Dispose();
  243. return;
  244. }
  245. RawBufferLen -= curlen;
  246. RawBufferStart += curlen;
  247. }
  248. */
  249. if(RawBufferLen > 0)
  250. {
  251. if (!m_context.Send(RawBuffer, RawBufferStart, RawBufferLen))
  252. {
  253. RawBuffer = null;
  254. RawBufferStart = -1;
  255. RawBufferLen = -1;
  256. if(m_body != null)
  257. m_body.Dispose();
  258. Sent = true;
  259. return;
  260. }
  261. }
  262. }
  263. RawBuffer = null;
  264. RawBufferStart = -1;
  265. RawBufferLen = -1;
  266. }
  267. if(m_body != null && m_body.Length > 0)
  268. {
  269. m_body.Flush();
  270. m_body.Seek(0, SeekOrigin.Begin);
  271. var buffer = new byte[8192];
  272. int bytesRead = m_body.Read(buffer, 0, 8192);
  273. while (bytesRead > 0)
  274. {
  275. if (!m_context.Send(buffer, 0, bytesRead))
  276. break;
  277. bytesRead = m_body.Read(buffer, 0, 8192);
  278. }
  279. m_body.Dispose();
  280. }
  281. Sent = true;
  282. m_context.ReqResponseSent(requestID, Connection);
  283. }
  284. /// <summary>
  285. /// Make sure that you have specified <see cref="ContentLength"/> and sent the headers first.
  286. /// </summary>
  287. /// <param name="buffer"></param>
  288. /// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
  289. /// <see cref="SendHeaders"/>
  290. /// <param name="offset">offset of first byte to send</param>
  291. /// <param name="count">number of bytes to send.</param>
  292. /// <seealso cref="Send"/>
  293. /// <seealso cref="SendHeaders"/>
  294. /// <remarks>This method can be used if you want to send body contents without caching them first. This
  295. /// is recommended for larger files to keep the memory usage low.</remarks>
  296. public bool SendBody(byte[] buffer, int offset, int count)
  297. {
  298. if (!HeadersSent)
  299. throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
  300. bool sent = m_context.Send(buffer, offset, count);
  301. Sent = true;
  302. if (sent)
  303. m_context.ReqResponseSent(requestID, Connection);
  304. return sent;
  305. }
  306. /// <summary>
  307. /// Make sure that you have specified <see cref="ContentLength"/> and sent the headers first.
  308. /// </summary>
  309. /// <param name="buffer"></param>
  310. /// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
  311. /// <see cref="SendHeaders"/>
  312. /// <seealso cref="Send"/>
  313. /// <seealso cref="SendHeaders"/>
  314. /// <remarks>This method can be used if you want to send body contents without caching them first. This
  315. /// is recommended for larger files to keep the memory usage low.</remarks>
  316. public bool SendBody(byte[] buffer)
  317. {
  318. if (!HeadersSent)
  319. throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
  320. bool sent = m_context.Send(buffer);
  321. if (sent)
  322. m_context.ReqResponseSent(requestID, Connection);
  323. Sent = true;
  324. return sent;
  325. }
  326. /// <summary>
  327. /// Send headers to the client.
  328. /// </summary>
  329. /// <exception cref="InvalidOperationException">If headers already been sent.</exception>
  330. /// <seealso cref="AddHeader"/>
  331. /// <seealso cref="Send"/>
  332. /// <seealso cref="SendBody(byte[])"/>
  333. public bool SendHeaders()
  334. {
  335. if (HeadersSent)
  336. throw new InvalidOperationException("Header have already been sent.");
  337. HeadersSent = true;
  338. if (m_headers["Date"] == null)
  339. m_headers["Date"] = DateTime.Now.ToString("r");
  340. if (m_headers["Content-Length"] == null)
  341. {
  342. int len = (int)m_contentLength;
  343. if(len == 0)
  344. {
  345. if(m_body != null)
  346. len = (int)m_body.Length;
  347. if(RawBuffer != null)
  348. len += RawBufferLen;
  349. }
  350. m_headers["Content-Length"] = len.ToString();
  351. }
  352. if (m_headers["Content-Type"] == null)
  353. m_headers["Content-Type"] = m_contentType ?? DefaultContentType;
  354. if (m_headers["Server"] == null)
  355. m_headers["Server"] = "Tiny WebServer";
  356. int keepaliveS = m_context.TimeoutKeepAlive / 1000;
  357. if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
  358. {
  359. m_headers["Keep-Alive"] = "timeout=" + keepaliveS + ", max=" + m_context.MAXRequests;
  360. m_headers["Connection"] = "Keep-Alive";
  361. }
  362. else
  363. m_headers["Connection"] = "close";
  364. var sb = new StringBuilder();
  365. sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status,
  366. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  367. for (int i = 0; i < m_headers.Count; ++i)
  368. {
  369. string headerName = m_headers.AllKeys[i];
  370. string[] values = m_headers.GetValues(i);
  371. if (values == null) continue;
  372. foreach (string value in values)
  373. sb.AppendFormat("{0}: {1}\r\n", headerName, value);
  374. }
  375. foreach (ResponseCookie cookie in Cookies)
  376. sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
  377. sb.Append("\r\n");
  378. m_headers.Clear();
  379. return m_context.Send(Encoding.GetBytes(sb.ToString()));
  380. }
  381. public byte[] GetHeaders()
  382. {
  383. HeadersSent = true;
  384. var sb = new StringBuilder();
  385. if(string.IsNullOrWhiteSpace(m_httpVersion))
  386. sb.AppendFormat("HTTP1/0 {0} {1}\r\n", (int)Status,
  387. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  388. else
  389. sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status,
  390. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  391. if (m_headers["Date"] == null)
  392. sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r"));
  393. if (m_headers["Content-Length"] == null)
  394. {
  395. long len = m_contentLength;
  396. if (len == 0)
  397. {
  398. len = Body.Length;
  399. if (RawBuffer != null && RawBufferLen > 0)
  400. len += RawBufferLen;
  401. }
  402. sb.AppendFormat("Content-Length: {0}\r\n", len);
  403. }
  404. if (m_headers["Content-Type"] == null)
  405. sb.AppendFormat("Content-Type: {0}\r\n", m_contentType ?? DefaultContentType);
  406. if (m_headers["Server"] == null)
  407. sb.Append("Server: OSWebServer\r\n");
  408. int keepaliveS = m_context.TimeoutKeepAlive / 1000;
  409. if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
  410. {
  411. sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MAXRequests);
  412. sb.Append("Connection: Keep-Alive\r\n");
  413. }
  414. else
  415. sb.Append("Connection: close\r\n");
  416. if (m_headers["Connection"] != null)
  417. m_headers["Connection"] = null;
  418. if (m_headers["Keep-Alive"] != null)
  419. m_headers["Keep-Alive"] = null;
  420. for (int i = 0; i < m_headers.Count; ++i)
  421. {
  422. string headerName = m_headers.AllKeys[i];
  423. string[] values = m_headers.GetValues(i);
  424. if (values == null) continue;
  425. foreach (string value in values)
  426. sb.AppendFormat("{0}: {1}\r\n", headerName, value);
  427. }
  428. foreach (ResponseCookie cookie in Cookies)
  429. sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
  430. sb.Append("\r\n");
  431. m_headers.Clear();
  432. return Encoding.GetBytes(sb.ToString());
  433. }
  434. public void Send()
  435. {
  436. if (Sent)
  437. throw new InvalidOperationException("Everything have already been sent.");
  438. if (m_context.MAXRequests == 0 || m_keepAlive == 0)
  439. {
  440. Connection = ConnectionType.Close;
  441. m_context.TimeoutKeepAlive = 0;
  442. }
  443. else
  444. {
  445. if (m_keepAlive > 0)
  446. m_context.TimeoutKeepAlive = m_keepAlive * 1000;
  447. }
  448. m_headerBytes = GetHeaders();
  449. if (RawBuffer != null)
  450. {
  451. if (RawBufferStart < 0 || RawBufferStart > RawBuffer.Length)
  452. return;
  453. if (RawBufferLen < 0)
  454. RawBufferLen = RawBuffer.Length;
  455. if (RawBufferLen + RawBufferStart > RawBuffer.Length)
  456. RawBufferLen = RawBuffer.Length - RawBufferStart;
  457. int tlen = m_headerBytes.Length + RawBufferLen;
  458. if(RawBufferLen > 0 && tlen < 16384)
  459. {
  460. byte[] tmp = new byte[tlen];
  461. Array.Copy(m_headerBytes, tmp, m_headerBytes.Length);
  462. Array.Copy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen);
  463. m_headerBytes = null;
  464. RawBuffer = tmp;
  465. RawBufferStart = 0;
  466. RawBufferLen = tlen;
  467. }
  468. }
  469. m_context.StartSendResponse(this);
  470. }
  471. public async Task SendNextAsync(int bytesLimit)
  472. {
  473. if (m_headerBytes != null)
  474. {
  475. if(!await m_context.SendAsync(m_headerBytes, 0, m_headerBytes.Length).ConfigureAwait(false))
  476. {
  477. if(m_body != null)
  478. m_body.Dispose();
  479. RawBuffer = null;
  480. Sent = true;
  481. return;
  482. }
  483. bytesLimit -= m_headerBytes.Length;
  484. m_headerBytes = null;
  485. if(bytesLimit <= 0)
  486. {
  487. m_context.ContinueSendResponse();
  488. return;
  489. }
  490. }
  491. if (RawBuffer != null)
  492. {
  493. if (RawBufferLen > 0)
  494. {
  495. bool sendRes;
  496. if(RawBufferLen > bytesLimit)
  497. {
  498. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
  499. RawBufferLen -= bytesLimit;
  500. RawBufferStart += bytesLimit;
  501. }
  502. else
  503. {
  504. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
  505. RawBufferLen = 0;
  506. }
  507. if (!sendRes)
  508. {
  509. RawBuffer = null;
  510. if(m_body != null)
  511. Body.Dispose();
  512. Sent = true;
  513. return;
  514. }
  515. }
  516. if (RawBufferLen <= 0)
  517. RawBuffer = null;
  518. else
  519. {
  520. m_context.ContinueSendResponse();
  521. return;
  522. }
  523. }
  524. if (m_body != null && m_body.Length != 0)
  525. {
  526. m_body.Flush();
  527. m_body.Seek(0, SeekOrigin.Begin);
  528. RawBuffer = new byte[m_body.Length];
  529. RawBufferLen = m_body.Read(RawBuffer, 0, (int)m_body.Length);
  530. m_body.Dispose();
  531. if(RawBufferLen > 0)
  532. {
  533. bool sendRes;
  534. if (RawBufferLen > bytesLimit)
  535. {
  536. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
  537. RawBufferLen -= bytesLimit;
  538. RawBufferStart += bytesLimit;
  539. }
  540. else
  541. {
  542. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
  543. RawBufferLen = 0;
  544. }
  545. if (!sendRes)
  546. {
  547. RawBuffer = null;
  548. Sent = true;
  549. return;
  550. }
  551. }
  552. if (RawBufferLen > 0)
  553. {
  554. m_context.ContinueSendResponse();
  555. return;
  556. }
  557. }
  558. if (m_body != null)
  559. m_body.Dispose();
  560. Sent = true;
  561. m_context.ReqResponseSent(requestID, Connection);
  562. }
  563. /// <summary>
  564. /// Redirect client to somewhere else using the 302 status code.
  565. /// </summary>
  566. /// <param name="uri">Destination of the redirect</param>
  567. /// <exception cref="InvalidOperationException">If headers already been sent.</exception>
  568. /// <remarks>You can not do anything more with the request when a redirect have been done. This should be your last
  569. /// action.</remarks>
  570. public void Redirect(Uri uri)
  571. {
  572. Status = HttpStatusCode.Redirect;
  573. m_headers["location"] = uri.ToString();
  574. }
  575. /// <summary>
  576. /// redirect to somewhere
  577. /// </summary>
  578. /// <param name="url">where the redirect should go</param>
  579. /// <remarks>
  580. /// No body are allowed when doing redirects.
  581. /// </remarks>
  582. public void Redirect(string url)
  583. {
  584. Status = HttpStatusCode.Redirect;
  585. m_headers["location"] = url;
  586. }
  587. public void Clear()
  588. {
  589. if(Body != null && Body.CanRead)
  590. Body.Dispose();
  591. }
  592. #endregion
  593. }
  594. }