HttpResponse.cs 19 KB

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