HttpResponse.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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. public event EventHandler<BandWitdhEventArgs> BandWitdhEvent;
  13. private const string DefaultContentType = "text/html;charset=UTF-8";
  14. private readonly IHttpClientContext m_context;
  15. private readonly ResponseCookies m_cookies = new ResponseCookies();
  16. private readonly NameValueCollection m_headers = new NameValueCollection();
  17. private string m_httpVersion;
  18. private Stream m_body;
  19. private long m_contentLength;
  20. private string m_contentType;
  21. private Encoding m_encoding = Encoding.UTF8;
  22. private int m_keepAlive = 60;
  23. public uint requestID { get; private set; }
  24. public byte[] RawBuffer { get; set; }
  25. public int RawBufferStart { get; set; }
  26. public int RawBufferLen { get; set; }
  27. public double RequestTS { get; private set; }
  28. internal byte[] m_headerBytes = null;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="IHttpResponse"/> class.
  31. /// </summary>
  32. /// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
  33. /// <param name="request">Contains information of what the client want to receive.</param>
  34. /// <exception cref="ArgumentException"><see cref="IHttpRequest.HttpVersion"/> cannot be empty.</exception>
  35. public HttpResponse(IHttpRequest request)
  36. {
  37. m_httpVersion = request.HttpVersion;
  38. if (string.IsNullOrEmpty(m_httpVersion))
  39. m_httpVersion = "HTTP/1.1";
  40. Status = HttpStatusCode.OK;
  41. m_context = request.Context;
  42. m_Connetion = request.Connection;
  43. requestID = request.ID;
  44. RequestTS = request.ArrivalTS;
  45. RawBufferStart = -1;
  46. RawBufferLen = -1;
  47. }
  48. /// <summary>
  49. /// Initializes a new instance of the <see cref="IHttpResponse"/> class.
  50. /// </summary>
  51. /// <param name="context">Client that send the <see cref="IHttpRequest"/>.</param>
  52. /// <param name="httpVersion">Version of HTTP protocol that the client uses.</param>
  53. /// <param name="connectionType">Type of HTTP connection used.</param>
  54. internal HttpResponse(IHttpClientContext context, string httpVersion, ConnectionType connectionType)
  55. {
  56. Status = HttpStatusCode.OK;
  57. m_context = context;
  58. m_httpVersion = httpVersion;
  59. m_Connetion = connectionType;
  60. }
  61. private ConnectionType m_Connetion;
  62. public ConnectionType Connection
  63. {
  64. get { return m_Connetion; }
  65. set { return; }
  66. }
  67. private int m_priority = 0;
  68. public int Priority
  69. {
  70. get { return m_priority;}
  71. set { m_priority = (value > 0 && m_priority < 3)? value : 0;}
  72. }
  73. #region IHttpResponse Members
  74. /// <summary>
  75. /// The body stream is used to cache the body contents
  76. /// before sending everything to the client. It's the simplest
  77. /// way to serve documents.
  78. /// </summary>
  79. public Stream Body
  80. {
  81. get
  82. {
  83. if(m_body == null)
  84. m_body = new MemoryStream();
  85. return m_body;
  86. }
  87. }
  88. /// <summary>
  89. /// The chunked encoding modifies the body of a message in order to
  90. /// transfer it as a series of chunks, each with its own size indicator,
  91. /// followed by an OPTIONAL trailer containing entity-header fields. This
  92. /// allows dynamically produced content to be transferred along with the
  93. /// information necessary for the recipient to verify that it has
  94. /// received the full message.
  95. /// </summary>
  96. public bool Chunked { get; set; }
  97. /// <summary>
  98. /// Defines the version of the HTTP Response for applications where it's required
  99. /// for this to be forced.
  100. /// </summary>
  101. public string ProtocolVersion
  102. {
  103. get { return m_httpVersion; }
  104. set { m_httpVersion = value; }
  105. }
  106. /// <summary>
  107. /// Encoding to use when sending stuff to the client.
  108. /// </summary>
  109. /// <remarks>Default is UTF8</remarks>
  110. public Encoding Encoding
  111. {
  112. get { return m_encoding; }
  113. set { m_encoding = value; }
  114. }
  115. /// <summary>
  116. /// Number of seconds to keep connection alive
  117. /// </summary>
  118. /// <remarks>Only used if Connection property is set to <see cref="ConnectionType.KeepAlive"/>.</remarks>
  119. public int KeepAlive
  120. {
  121. get { return m_keepAlive; }
  122. set
  123. {
  124. if (value > 400)
  125. m_keepAlive = 400;
  126. else if (value <= 0)
  127. m_keepAlive = 0;
  128. else
  129. m_keepAlive = value;
  130. }
  131. }
  132. /// <summary>
  133. /// Status code that is sent to the client.
  134. /// </summary>
  135. /// <remarks>Default is <see cref="HttpStatusCode.OK"/></remarks>
  136. public HttpStatusCode Status { get; set; }
  137. /// <summary>
  138. /// Information about why a specific status code was used.
  139. /// </summary>
  140. public string Reason { get; set; }
  141. /// <summary>
  142. /// Size of the body. MUST be specified before sending the header,
  143. /// </summary>
  144. public long ContentLength
  145. {
  146. get { return m_contentLength; }
  147. set { m_contentLength = value; }
  148. }
  149. /// <summary>
  150. /// Kind of content
  151. /// </summary>
  152. /// <remarks>Default type is "text/html"</remarks>
  153. public string ContentType
  154. {
  155. get { return m_contentType; }
  156. set { m_contentType = value; }
  157. }
  158. /// <summary>
  159. /// Headers have been sent to the client-
  160. /// </summary>
  161. /// <remarks>You can not send any additional headers if they have already been sent.</remarks>
  162. public bool HeadersSent { get; private set; }
  163. /// <summary>
  164. /// The whole response have been sent.
  165. /// </summary>
  166. public bool Sent { get; private set; }
  167. /// <summary>
  168. /// Cookies that should be created/changed.
  169. /// </summary>
  170. public ResponseCookies Cookies
  171. {
  172. get { return m_cookies; }
  173. }
  174. /// <summary>
  175. /// Add another header to the document.
  176. /// </summary>
  177. /// <param name="name">Name of the header, case sensitive, use lower cases.</param>
  178. /// <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>
  179. /// <exception cref="InvalidOperationException">If headers already been sent.</exception>
  180. /// <exception cref="ArgumentException">If value conditions have not been met.</exception>
  181. /// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
  182. public void AddHeader(string name, string value)
  183. {
  184. if (HeadersSent)
  185. throw new InvalidOperationException("Headers have already been sent.");
  186. for (int i = 1; i < value.Length; ++i)
  187. {
  188. if (value[i] == '\r' && !char.IsWhiteSpace(value[i - 1]))
  189. throw new ArgumentException("New line in value do not start with a white space.");
  190. if (value[i] == '\n' && value[i - 1] != '\r')
  191. throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
  192. }
  193. m_headers[name] = value;
  194. }
  195. public byte[] GetHeaders()
  196. {
  197. HeadersSent = true;
  198. var sb = new StringBuilder();
  199. if(string.IsNullOrWhiteSpace(m_httpVersion))
  200. sb.AppendFormat("HTTP/1.1 {0} {1}\r\n", (int)Status,
  201. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  202. else
  203. sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status,
  204. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  205. if (m_headers["Date"] == null)
  206. sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r"));
  207. if (m_headers["Content-Length"] == null)
  208. {
  209. long len = m_contentLength;
  210. if (len == 0)
  211. {
  212. len = Body.Length;
  213. if (RawBuffer != null && RawBufferLen > 0)
  214. len += RawBufferLen;
  215. }
  216. sb.AppendFormat("Content-Length: {0}\r\n", len);
  217. }
  218. if (m_headers["Content-Type"] == null)
  219. sb.AppendFormat("Content-Type: {0}\r\n", m_contentType ?? DefaultContentType);
  220. if (m_headers["Server"] == null)
  221. sb.Append("Server: OSWebServer\r\n");
  222. if(Status != HttpStatusCode.OK)
  223. {
  224. sb.Append("Connection: close\r\n");
  225. Connection = ConnectionType.Close;
  226. }
  227. else
  228. {
  229. int keepaliveS = m_context.TimeoutKeepAlive / 1000;
  230. if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MaxRequests > 0)
  231. {
  232. sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MaxRequests);
  233. sb.Append("Connection: Keep-Alive\r\n");
  234. }
  235. else
  236. {
  237. sb.Append("Connection: close\r\n");
  238. Connection = ConnectionType.Close;
  239. }
  240. }
  241. if (m_headers["Connection"] != null)
  242. m_headers["Connection"] = null;
  243. if (m_headers["Keep-Alive"] != null)
  244. m_headers["Keep-Alive"] = null;
  245. for (int i = 0; i < m_headers.Count; ++i)
  246. {
  247. string headerName = m_headers.AllKeys[i];
  248. string[] values = m_headers.GetValues(i);
  249. if (values == null) continue;
  250. foreach (string value in values)
  251. sb.AppendFormat("{0}: {1}\r\n", headerName, value);
  252. }
  253. foreach (ResponseCookie cookie in Cookies)
  254. sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
  255. sb.Append("\r\n");
  256. m_headers.Clear();
  257. return Encoding.GetBytes(sb.ToString());
  258. }
  259. public void Send()
  260. {
  261. if(m_context.IsClosing)
  262. return;
  263. if (Sent)
  264. throw new InvalidOperationException("Everything have already been sent.");
  265. if (m_context.MaxRequests == 0 || m_keepAlive == 0)
  266. {
  267. Connection = ConnectionType.Close;
  268. m_context.TimeoutKeepAlive = 0;
  269. }
  270. else
  271. {
  272. if (m_keepAlive > 0)
  273. m_context.TimeoutKeepAlive = m_keepAlive * 1000;
  274. }
  275. if (RawBuffer != null)
  276. {
  277. if (RawBufferStart > RawBuffer.Length)
  278. return;
  279. if (RawBufferStart < 0)
  280. RawBufferStart = 0;
  281. if (RawBufferLen < 0)
  282. RawBufferLen = RawBuffer.Length;
  283. if (RawBufferLen + RawBufferStart > RawBuffer.Length)
  284. RawBufferLen = RawBuffer.Length - RawBufferStart;
  285. }
  286. m_headerBytes = GetHeaders();
  287. if (RawBuffer != null)
  288. {
  289. int tlen = m_headerBytes.Length + RawBufferLen;
  290. if(RawBufferLen > 0 && tlen < 16384)
  291. {
  292. byte[] tmp = new byte[tlen];
  293. Array.Copy(m_headerBytes, tmp, m_headerBytes.Length);
  294. Array.Copy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen);
  295. m_headerBytes = null;
  296. RawBuffer = tmp;
  297. RawBufferStart = 0;
  298. RawBufferLen = tlen;
  299. }
  300. }
  301. m_context.StartSendResponse(this);
  302. }
  303. public async Task SendNextAsync(int bytesLimit)
  304. {
  305. if (m_headerBytes != null)
  306. {
  307. if(!await m_context.SendAsync(m_headerBytes, 0, m_headerBytes.Length).ConfigureAwait(false))
  308. {
  309. if (m_context.CanSend())
  310. {
  311. m_context.ContinueSendResponse(true);
  312. return;
  313. }
  314. if (m_body != null)
  315. m_body.Dispose();
  316. RawBuffer = null;
  317. Sent = true;
  318. return;
  319. }
  320. bytesLimit -= m_headerBytes.Length;
  321. m_headerBytes = null;
  322. if(bytesLimit <= 0)
  323. {
  324. m_context.ContinueSendResponse(true);
  325. return;
  326. }
  327. }
  328. if (RawBuffer != null)
  329. {
  330. if (RawBufferLen > 0)
  331. {
  332. if(BandWitdhEvent!=null)
  333. bytesLimit = CheckBandwidth(RawBufferLen, bytesLimit);
  334. bool sendRes;
  335. if(RawBufferLen > bytesLimit)
  336. {
  337. sendRes = (await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false));
  338. if (sendRes)
  339. {
  340. RawBufferLen -= bytesLimit;
  341. RawBufferStart += bytesLimit;
  342. }
  343. }
  344. else
  345. {
  346. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
  347. if(sendRes)
  348. RawBufferLen = 0;
  349. }
  350. if (!sendRes)
  351. {
  352. if (m_context.CanSend())
  353. {
  354. m_context.ContinueSendResponse(true);
  355. return;
  356. }
  357. RawBuffer = null;
  358. if(m_body != null)
  359. Body.Dispose();
  360. Sent = true;
  361. return;
  362. }
  363. }
  364. if (RawBufferLen <= 0)
  365. RawBuffer = null;
  366. else
  367. {
  368. m_context.ContinueSendResponse(true);
  369. return;
  370. }
  371. }
  372. if (m_body != null && m_body.Length != 0)
  373. {
  374. MemoryStream mb = m_body as MemoryStream;
  375. RawBuffer = mb.GetBuffer();
  376. RawBufferStart = 0; // must be a internal buffer, or starting at 0
  377. RawBufferLen = (int)mb.Length;
  378. mb.Dispose();
  379. m_body = null;
  380. if(RawBufferLen > 0)
  381. {
  382. bool sendRes;
  383. if (RawBufferLen > bytesLimit)
  384. {
  385. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
  386. if (sendRes)
  387. {
  388. RawBufferLen -= bytesLimit;
  389. RawBufferStart += bytesLimit;
  390. }
  391. }
  392. else
  393. {
  394. sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
  395. if (sendRes)
  396. RawBufferLen = 0;
  397. }
  398. if (!sendRes)
  399. {
  400. if (m_context.CanSend())
  401. {
  402. m_context.ContinueSendResponse(true);
  403. return;
  404. }
  405. RawBuffer = null;
  406. Sent = true;
  407. return;
  408. }
  409. }
  410. if (RawBufferLen > 0)
  411. {
  412. m_context.ContinueSendResponse(false);
  413. return;
  414. }
  415. }
  416. if (m_body != null)
  417. m_body.Dispose();
  418. Sent = true;
  419. await m_context.EndSendResponse(requestID, Connection).ConfigureAwait(false);
  420. }
  421. private int CheckBandwidth(int request, int bytesLimit)
  422. {
  423. if(request > bytesLimit)
  424. request = bytesLimit;
  425. var args = new BandWitdhEventArgs(request);
  426. BandWitdhEvent?.Invoke(this, args);
  427. if(args.Result > 8196)
  428. return args.Result;
  429. return 8196;
  430. }
  431. public void Clear()
  432. {
  433. if(Body != null && Body.CanRead)
  434. Body.Dispose();
  435. }
  436. #endregion
  437. }
  438. }