HttpResponse.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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 ResponseCookies();
  14. private readonly NameValueCollection m_headers = new NameValueCollection();
  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 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(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. if(m_body == null)
  82. m_body = new MemoryStream();
  83. return m_body;
  84. }
  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. /// </summary>
  142. public long ContentLength
  143. {
  144. get { return m_contentLength; }
  145. set { m_contentLength = value; }
  146. }
  147. /// <summary>
  148. /// Kind of content
  149. /// </summary>
  150. /// <remarks>Default type is "text/html"</remarks>
  151. public string ContentType
  152. {
  153. get { return m_contentType; }
  154. set { m_contentType = value; }
  155. }
  156. /// <summary>
  157. /// Headers have been sent to the client-
  158. /// </summary>
  159. /// <remarks>You can not send any additional headers if they have already been sent.</remarks>
  160. public bool HeadersSent { get; private set; }
  161. /// <summary>
  162. /// The whole response have been sent.
  163. /// </summary>
  164. public bool Sent { get; private set; }
  165. /// <summary>
  166. /// Cookies that should be created/changed.
  167. /// </summary>
  168. public ResponseCookies Cookies
  169. {
  170. get { return m_cookies; }
  171. }
  172. /// <summary>
  173. /// Set response as a http redirect
  174. /// </summary>
  175. /// <param name="url">redirection target url</param>
  176. /// <param name="redirStatusCode">the response Status, must be Found, Redirect, Moved,MovedPermanently,RedirectKeepVerb, RedirectMethod, TemporaryRedirect. Defaults to Redirect</param>
  177. public void Redirect(string url, HttpStatusCode redirStatusCode = HttpStatusCode.Redirect)
  178. {
  179. if (HeadersSent)
  180. throw new InvalidOperationException("Headers have already been sent.");
  181. m_headers["Location"] = url;
  182. Status = redirStatusCode;
  183. }
  184. /// <summary>
  185. /// Add another header to the document.
  186. /// </summary>
  187. /// <param name="name">Name of the header, case sensitive.</param>
  188. /// <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>
  189. /// <exception cref="InvalidOperationException">If headers already been sent.</exception>
  190. /// <exception cref="ArgumentException">If value conditions have not been met.</exception>
  191. /// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
  192. public void AddHeader(string name, string value)
  193. {
  194. if (HeadersSent)
  195. throw new InvalidOperationException("Headers have already been sent.");
  196. for (int i = 1; i < value.Length; ++i)
  197. {
  198. if (value[i] == '\r' && !char.IsWhiteSpace(value[i - 1]))
  199. throw new ArgumentException("New line in value do not start with a white space.");
  200. if (value[i] == '\n' && value[i - 1] != '\r')
  201. throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
  202. }
  203. m_headers[name] = value;
  204. }
  205. public byte[] GetHeaders()
  206. {
  207. HeadersSent = true;
  208. var sb = osStringBuilderCache.Acquire();
  209. if(string.IsNullOrWhiteSpace(m_httpVersion))
  210. sb.AppendFormat("HTTP/1.1 {0} {1}\r\n", (int)Status,
  211. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  212. else
  213. sb.AppendFormat("{0} {1} {2}\r\n", m_httpVersion, (int)Status,
  214. string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
  215. sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r"));
  216. long len = 0;
  217. if(m_body!= null)
  218. len = m_body.Length;
  219. if (RawBuffer != null && RawBufferLen > 0)
  220. len += RawBufferLen;
  221. sb.AppendFormat("Content-Length: {0}\r\n", len);
  222. if (m_headers["Content-Type"] == null)
  223. sb.AppendFormat("Content-Type: {0}\r\n", m_contentType ?? DefaultContentType);
  224. switch(Status)
  225. {
  226. case HttpStatusCode.OK:
  227. case HttpStatusCode.PartialContent:
  228. case HttpStatusCode.Accepted:
  229. case HttpStatusCode.Continue:
  230. case HttpStatusCode.Found:
  231. {
  232. int keepaliveS = m_context.TimeoutKeepAlive / 1000;
  233. if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MaxRequests > 0)
  234. {
  235. sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MaxRequests);
  236. sb.Append("Connection: Keep-Alive\r\n");
  237. }
  238. else
  239. {
  240. sb.Append("Connection: close\r\n");
  241. Connection = ConnectionType.Close;
  242. }
  243. break;
  244. }
  245. default:
  246. sb.Append("Connection: close\r\n");
  247. Connection = ConnectionType.Close;
  248. break;
  249. }
  250. for (int i = 0; i < m_headers.Count; ++i)
  251. {
  252. string headerName = m_headers.AllKeys[i];
  253. switch(headerName)
  254. {
  255. case "Connection":
  256. case "Content-Length":
  257. case "Date":
  258. case "Keep-Alive":
  259. case "Server":
  260. continue;
  261. }
  262. string[] values = m_headers.GetValues(i);
  263. if (values == null) continue;
  264. foreach (string value in values)
  265. sb.AppendFormat("{0}: {1}\r\n", headerName, value);
  266. }
  267. sb.Append("Server: OSWebServer\r\n");
  268. foreach (ResponseCookie cookie in Cookies)
  269. sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
  270. sb.Append("\r\n");
  271. m_headers.Clear();
  272. return Encoding.GetBytes(osStringBuilderCache.GetStringAndRelease(sb));
  273. }
  274. public void Send()
  275. {
  276. if(m_context.IsClosing)
  277. return;
  278. if (Sent)
  279. throw new InvalidOperationException("Everything have already been sent.");
  280. if (m_context.MaxRequests == 0 || m_keepAlive == 0)
  281. {
  282. Connection = ConnectionType.Close;
  283. m_context.TimeoutKeepAlive = 0;
  284. }
  285. else
  286. {
  287. if (m_keepAlive > 0)
  288. m_context.TimeoutKeepAlive = m_keepAlive * 1000;
  289. }
  290. if (RawBuffer != null)
  291. {
  292. if (RawBufferStart > RawBuffer.Length)
  293. return;
  294. if (RawBufferStart < 0)
  295. RawBufferStart = 0;
  296. if (RawBufferLen < 0)
  297. RawBufferLen = RawBuffer.Length;
  298. if (RawBufferLen + RawBufferStart > RawBuffer.Length)
  299. RawBufferLen = RawBuffer.Length - RawBufferStart;
  300. }
  301. m_headerBytes = GetHeaders();
  302. if (RawBuffer != null && RawBufferLen > 0)
  303. {
  304. int tlen = m_headerBytes.Length + RawBufferLen;
  305. if(tlen < 8 * 1024)
  306. {
  307. byte[] tmp = new byte[tlen];
  308. Buffer.BlockCopy(m_headerBytes, 0, tmp, 0, m_headerBytes.Length);
  309. Buffer.BlockCopy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen);
  310. m_headerBytes = null;
  311. RawBuffer = tmp;
  312. RawBufferStart = 0;
  313. RawBufferLen = tlen;
  314. }
  315. if (RawBufferLen == 0)
  316. RawBuffer = null;
  317. }
  318. if (m_body != null && m_body.Length == 0)
  319. {
  320. m_body.Dispose();
  321. m_body = null;
  322. }
  323. if (m_headerBytes == null && RawBuffer == null && m_body == null)
  324. {
  325. Sent = true;
  326. m_context.EndSendResponse(requestID, Connection);
  327. }
  328. else
  329. m_context.StartSendResponse(this);
  330. }
  331. public bool SendNextAsync(int bytesLimit)
  332. {
  333. if (m_headerBytes != null)
  334. {
  335. byte[] b = m_headerBytes;
  336. m_headerBytes = null;
  337. if (!m_context.SendAsyncStart(b, 0, b.Length))
  338. {
  339. if (m_body != null)
  340. {
  341. m_body.Dispose();
  342. m_body = null;
  343. }
  344. RawBuffer = null;
  345. Sent = true;
  346. return false;
  347. }
  348. return true;
  349. }
  350. bool sendRes;
  351. if (RawBuffer != null)
  352. {
  353. if(RawBufferLen > 0)
  354. {
  355. byte[] b = RawBuffer;
  356. int s = RawBufferStart;
  357. if (RawBufferLen > bytesLimit)
  358. {
  359. RawBufferLen -= bytesLimit;
  360. RawBufferStart += bytesLimit;
  361. if (RawBufferLen <= 0)
  362. RawBuffer = null;
  363. sendRes = m_context.SendAsyncStart(b, s, bytesLimit);
  364. }
  365. else
  366. {
  367. int l = RawBufferLen;
  368. RawBufferLen = 0;
  369. RawBuffer = null;
  370. sendRes = m_context.SendAsyncStart(b, s, l);
  371. }
  372. if (!sendRes)
  373. {
  374. RawBuffer = null;
  375. if(m_body != null)
  376. {
  377. m_body.Dispose();
  378. m_body = null;
  379. }
  380. Sent = true;
  381. return false;
  382. }
  383. return true;
  384. }
  385. else
  386. RawBuffer = null;
  387. }
  388. if (m_body != null)
  389. {
  390. if(m_body.Length != 0)
  391. {
  392. MemoryStream mb = m_body as MemoryStream;
  393. RawBuffer = mb.GetBuffer();
  394. RawBufferStart = 0; // must be a internal buffer, or starting at 0
  395. RawBufferLen = (int)mb.Length;
  396. m_body.Dispose();
  397. m_body = null;
  398. if (RawBufferLen > 0)
  399. {
  400. byte[] b = RawBuffer;
  401. int s = RawBufferStart;
  402. if (RawBufferLen > bytesLimit)
  403. {
  404. RawBufferLen -= bytesLimit;
  405. RawBufferStart += bytesLimit;
  406. if (RawBufferLen <= 0)
  407. RawBuffer = null;
  408. sendRes = m_context.SendAsyncStart(b, s, bytesLimit);
  409. }
  410. else
  411. {
  412. int l = RawBufferLen;
  413. sendRes = m_context.SendAsyncStart(b, s, l);
  414. RawBufferLen = 0;
  415. RawBuffer = null;
  416. }
  417. if (!sendRes)
  418. {
  419. RawBuffer = null;
  420. Sent = true;
  421. return false;
  422. }
  423. return true;
  424. }
  425. else
  426. RawBuffer = null;
  427. }
  428. else
  429. {
  430. m_body.Dispose();
  431. m_body = null;
  432. }
  433. }
  434. Sent = true;
  435. m_context.EndSendResponse(requestID, Connection);
  436. return false;
  437. }
  438. public void CheckSendNextAsyncContinue()
  439. {
  440. if(m_headerBytes == null && RawBuffer == null && m_body == null)
  441. {
  442. Sent = true;
  443. m_context.EndSendResponse(requestID, Connection);
  444. }
  445. else
  446. {
  447. m_context.ContinueSendResponse();
  448. }
  449. }
  450. public void Clear()
  451. {
  452. if(m_body != null && m_body.CanRead)
  453. m_body.Dispose();
  454. }
  455. #endregion
  456. }
  457. }