PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/PeerCastStation/PeerCastStation.HTTP/HTTPSourceStream.cs

https://github.com/kumaryu/peercaststation
C# | 281 lines | 253 code | 22 blank | 6 comment | 25 complexity | 1dd75dfee2f51367f7ff6d0d9fede1d8 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using PeerCastStation.Core;
  5. using System.IO;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace PeerCastStation.HTTP
  12. {
  13. /// <summary>
  14. ///サーバからのHTTPレスポンス内容を保持するクラスです
  15. /// </summary>
  16. /// <summary>
  17. /// ストリームからHTTPレスポンスを読み取るクラスです
  18. /// </summary>
  19. public static class HTTPResponseReader
  20. {
  21. public static async Task<HTTPResponse> ReadAsync(Stream stream, CancellationToken cancel_token)
  22. {
  23. string line = null;
  24. var requests = new List<string>();
  25. var buf = new List<byte>();
  26. var length = 0;
  27. while (line!="") {
  28. var value = await stream.ReadByteAsync(cancel_token).ConfigureAwait(false);
  29. if (value<0) {
  30. throw new EndOfStreamException();
  31. }
  32. buf.Add((byte)value);
  33. length += 1;
  34. if (buf.Count>=2 && buf[buf.Count-2]=='\r' && buf[buf.Count-1]=='\n') {
  35. line = System.Text.Encoding.UTF8.GetString(buf.ToArray(), 0, buf.Count - 2);
  36. if (line!="") requests.Add(line);
  37. buf.Clear();
  38. }
  39. else if (length>4096) {
  40. throw new InvalidDataException();
  41. }
  42. }
  43. var headers = new Dictionary<string, string>();
  44. var protocol = "";
  45. var status = 200;
  46. var reason_phrase = "";
  47. foreach (var req in requests) {
  48. Match match = null;
  49. if ((match = Regex.Match(req, @"^(HTTP/1.\d) (\d+) (.*)$")).Success) {
  50. protocol = match.Groups[1].Value;
  51. status = Int32.Parse(match.Groups[2].Value);
  52. reason_phrase = match.Groups[3].Value;
  53. }
  54. else if ((match = Regex.Match(req, @"^(\S*):\s*(.*)\s*$", RegexOptions.IgnoreCase)).Success) {
  55. headers[match.Groups[1].Value.ToUpperInvariant()] = match.Groups[2].Value;
  56. }
  57. }
  58. return new HTTPResponse(protocol, status, reason_phrase, headers, null);
  59. }
  60. }
  61. public class HTTPSourceStreamFactory
  62. : SourceStreamFactoryBase
  63. {
  64. public override string Name { get { return "http"; } }
  65. public override string Scheme { get { return "http"; } }
  66. public override SourceStreamType Type {
  67. get { return SourceStreamType.Broadcast; }
  68. }
  69. public override Uri DefaultUri {
  70. get { return new Uri("http://localhost:8080/"); }
  71. }
  72. public override bool IsContentReaderRequired {
  73. get { return true; }
  74. }
  75. public override ISourceStream Create(Channel channel, Uri source, IContentReader reader)
  76. {
  77. return new HTTPSourceStream(this.PeerCast, channel, source, reader);
  78. }
  79. public HTTPSourceStreamFactory(PeerCast peercast)
  80. : base(peercast)
  81. {
  82. }
  83. }
  84. public class HTTPSourceConnection
  85. : SourceConnectionBase
  86. {
  87. private IContentReader contentReader;
  88. private BufferedContentSink contentSink;
  89. private HTTPResponse response = null;
  90. public HTTPSourceConnection(
  91. PeerCast peercast,
  92. Channel channel,
  93. Uri source_uri,
  94. IContentReader content_reader,
  95. bool use_content_bitrate)
  96. : base(peercast, channel, source_uri)
  97. {
  98. contentReader = content_reader;
  99. contentSink = new BufferedContentSink(new AsynchronousContentSink(new ChannelContentSink(channel, use_content_bitrate)));
  100. }
  101. protected override async Task<SourceConnectionClient> DoConnect(Uri source, CancellationToken cancel_token)
  102. {
  103. try {
  104. var client = new TcpClient();
  105. if (source.HostNameType==UriHostNameType.IPv4 ||
  106. source.HostNameType==UriHostNameType.IPv6) {
  107. await client.ConnectAsync(IPAddress.Parse(source.Host), source.Port).ConfigureAwait(false);
  108. }
  109. else {
  110. await client.ConnectAsync(source.DnsSafeHost, source.Port).ConfigureAwait(false);
  111. }
  112. var connection = new SourceConnectionClient(client);
  113. connection.Stream.ReadTimeout = 10000;
  114. connection.Stream.WriteTimeout = 8000;
  115. return connection;
  116. }
  117. catch (SocketException e) {
  118. Logger.Error(e);
  119. return null;
  120. }
  121. }
  122. protected override async Task DoProcess(CancellationToken cancel_token)
  123. {
  124. try {
  125. this.Status = ConnectionStatus.Connecting;
  126. var host = SourceUri.DnsSafeHost;
  127. if (SourceUri.Port!=-1 && SourceUri.Port!=80) {
  128. host = $"{SourceUri.DnsSafeHost}:{SourceUri.Port}";
  129. }
  130. var request =
  131. $"GET {SourceUri.PathAndQuery} HTTP/1.1\r\n" +
  132. $"Host: {host}\r\n" +
  133. "Accept: */*\r\n" +
  134. "User-Agent: NSPlayer/12.0\r\n" +
  135. "Connection: close\r\n" +
  136. "Cache-Control: no-cache\r\n" +
  137. "Pragma: xPlayStrm=1\r\n" +
  138. "Pragma: rate=1.000,stream-time=0\r\n" +
  139. "\r\n";
  140. await connection.Stream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(request)).ConfigureAwait(false);
  141. Logger.Debug("Sending request:\n" + request);
  142. response = null;
  143. response = await HTTPResponseReader.ReadAsync(connection.Stream, cancel_token).ConfigureAwait(false);
  144. if (response.Status!=200) {
  145. Logger.Error("Server responses {0} to GET {1}", response.Status, SourceUri.PathAndQuery);
  146. Stop(response.Status==404 ? StopReason.OffAir : StopReason.UnavailableError);
  147. }
  148. this.Status = ConnectionStatus.Connected;
  149. await contentReader.ReadAsync(contentSink, connection.Stream, cancel_token).ConfigureAwait(false);
  150. Stop(StopReason.OffAir);
  151. }
  152. catch (InvalidDataException) {
  153. Stop(StopReason.ConnectionError);
  154. }
  155. catch (OperationCanceledException) {
  156. Logger.Error("Recv content timed out");
  157. Stop(StopReason.ConnectionError);
  158. }
  159. catch (IOException) {
  160. Stop(StopReason.ConnectionError);
  161. }
  162. this.Status = ConnectionStatus.Error;
  163. }
  164. public override ConnectionInfo GetConnectionInfo()
  165. {
  166. IPEndPoint endpoint = connection!=null ? connection.RemoteEndPoint : null;
  167. string server_name = "";
  168. if (response==null || !response.Headers.TryGetValue("SERVER", out server_name)) {
  169. server_name = "";
  170. }
  171. return new ConnectionInfoBuilder {
  172. ProtocolName = "HTTP Source",
  173. Type = ConnectionType.Source,
  174. Status = Status,
  175. RemoteName = SourceUri.ToString(),
  176. RemoteEndPoint = endpoint,
  177. RemoteHostStatus = (endpoint!=null && endpoint.Address.IsSiteLocal()) ? RemoteHostStatus.Local : RemoteHostStatus.None,
  178. ContentPosition = contentSink.LastContent?.Position ?? 0,
  179. RecvRate = RecvRate,
  180. SendRate = SendRate,
  181. AgentName = server_name,
  182. }.Build();
  183. }
  184. }
  185. public class HTTPSourceStream
  186. : SourceStreamBase
  187. {
  188. public IContentReader ContentReader { get; private set; }
  189. public bool UseContentBitrate { get; private set; }
  190. public HTTPSourceStream(PeerCast peercast, Channel channel, Uri source_uri, IContentReader reader)
  191. : base(peercast, channel, source_uri)
  192. {
  193. this.ContentReader = reader;
  194. this.UseContentBitrate = channel.ChannelInfo==null || channel.ChannelInfo.Bitrate==0;
  195. }
  196. public override ConnectionInfo GetConnectionInfo()
  197. {
  198. var conn = sourceConnection;
  199. if (!conn.IsCompleted) {
  200. return conn.Connection.GetConnectionInfo();
  201. }
  202. else {
  203. ConnectionStatus status;
  204. switch (StoppedReason) {
  205. case StopReason.UserReconnect: status = ConnectionStatus.Connecting; break;
  206. case StopReason.UserShutdown: status = ConnectionStatus.Idle; break;
  207. default: status = ConnectionStatus.Error; break;
  208. }
  209. IPEndPoint endpoint = null;
  210. string server_name = "";
  211. return new ConnectionInfoBuilder {
  212. ProtocolName = "HTTP Source",
  213. Type = ConnectionType.Source,
  214. Status = status,
  215. RemoteName = SourceUri.ToString(),
  216. RemoteEndPoint = endpoint,
  217. RemoteHostStatus = RemoteHostStatus.None,
  218. AgentName = server_name,
  219. }.Build();
  220. }
  221. }
  222. protected override ISourceConnection CreateConnection(Uri source_uri)
  223. {
  224. return new HTTPSourceConnection(PeerCast, Channel, source_uri, ContentReader, UseContentBitrate);
  225. }
  226. protected override void OnConnectionStopped(ISourceConnection connection, ConnectionStoppedArgs args)
  227. {
  228. switch (args.Reason) {
  229. case StopReason.UserReconnect:
  230. case StopReason.UserShutdown:
  231. break;
  232. default:
  233. args.Delay = 3000;
  234. args.Reconnect = true;
  235. break;
  236. }
  237. }
  238. public override SourceStreamType Type
  239. {
  240. get { return SourceStreamType.Broadcast; }
  241. }
  242. }
  243. [Plugin]
  244. class HTTPSourceStreamPlugin
  245. : PluginBase
  246. {
  247. override public string Name { get { return "HTTP Source"; } }
  248. private HTTPSourceStreamFactory factory;
  249. override protected void OnAttach()
  250. {
  251. if (factory==null) factory = new HTTPSourceStreamFactory(Application.PeerCast);
  252. Application.PeerCast.SourceStreamFactories.Add(factory);
  253. }
  254. override protected void OnDetach()
  255. {
  256. Application.PeerCast.SourceStreamFactories.Remove(factory);
  257. }
  258. }
  259. }