Esempio n. 1
0
        void ClientRequestThread()
        {
            // Copy the socket reference.
            _clientSocket     = _newRequestSocket;
            _newRequestSocket = null;
            try
            {
                // Receive HTTP Request from Web Browser
                byte[] recvBytes = new byte[1025];
                int    bytes     = 0;
                int    retries   = 0;
                while (bytes == 0)
                {
                    bytes = _clientSocket.Receive(recvBytes, 0, _clientSocket.Available, SocketFlags.None);

#if DEBUG
                    if (Log.IsDebugEnabled)
                    {
                        Log.Debug("Received " + bytes + " bytes.");
                    }
#endif

                    if (retries++ > 100)
                    {
                        throw new Exception("Exceeded maximum number of retries for receiving HTTP request.");
                    }

                    if (bytes == 0)
                    {
                        Thread.Sleep(10);
                    }
                }

                string htmlReq = Encoding.ASCII.GetString(recvBytes, 0, bytes);

#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("HTTP Request: " + htmlReq.Trim());
                }
#endif

                // Ensure that GET method is used.
                string[] strArray = htmlReq.Trim().Split(' ');
                string   method   = strArray[0].Trim().ToUpper();
                if (method != "GET")
                {
#if DEBUG
                    if (Log.IsWarnEnabled)
                    {
                        if (method.Trim().Length == 0)
                        {
                            Log.Warn("Request method not found.");
                        }
                        else
                        {
                            Log.Warn("Not GET method. Method = " + method);
                        }
                    }
#endif

                    SendForbiddenResponse();
                    goto EndResponse;
                }

                // Put the HTTP headers into a hashtable.
                string   headers = htmlReq.Replace("\n\r", "\n");
                string[] lines   = headers.Split('\n');
                Dictionary <string, string> table = new Dictionary <string, string>();
                int i = 0;
                foreach (string s in lines)
                {
                    // Ignore first line and empty lines.
                    if (i++ == 0 || s.Length == 0)
                    {
                        continue;
                    }
                    try
                    {
#if DEBUG
                        if (Log.IsDebugEnabled)
                        {
                            Log.Debug("Line = \"" + s + "\"");
                        }
#endif

                        int pos = s.IndexOf(':');
                        if (pos >= 0)
                        {
                            string name = s.Substring(0, pos).Trim();
                            string val  = s.Substring(pos + 1).Trim();
                            table.Add(name, val);
                        }
                        else
                        {
#if DEBUG
                            if (Log.IsWarnEnabled)
                            {
                                Log.Warn("Unexpected header string \"" + s + "\"");
                            }
#endif
                        }
                    }
                    catch (Exception ex)
                    {
#if DEBUG
                        if (Log.IsErrorEnabled)
                        {
                            Log.Error("Exception", ex);
                        }
#endif
                    }
                }

                // Check user-agent.
                // It's ok if there isn't one (Playdar doesn't provide one, unusually).
                if (table.ContainsKey("user-agent"))
                {
                    // Get the name of the agent, without version and other information.
                    string userAgent = table["user-agent"];
                    String agent     = userAgent.IndexOf('/') < 0 ? userAgent : userAgent.Substring(0, userAgent.IndexOf('/'));

#if DEBUG
                    if (Log.IsDebugEnabled)
                    {
                        Log.Debug("Agent = \"" + agent + "\".");
                    }
#endif

                    // TODO: Allow certain user-agent, but not others.

                    /*
                     *                  if (agent != "Something") {
                     *      SendForbiddenResponse();
                     *      goto EndResponse;
                     *                  }
                     */
                }

                // Get request URI.
                if (strArray.Length == 1)
                {
#if DEBUG
                    if (Log.IsWarnEnabled)
                    {
                        Log.Warn("Request URI not found.");
                    }
#endif

                    SendForbiddenResponse();
                    goto EndResponse;
                }

                string uri = strArray[1].Trim();
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("URI = " + uri);
                }
#endif

                if (uri.Length != BasePath.Length + 36)
                {
#if DEBUG
                    if (Log.IsErrorEnabled)
                    {
                        Log.Error("URI length isn't correct. URI = \"" + uri + '"');
                    }
#endif

                    SendForbiddenResponse();
                    goto EndResponse;
                }

                // Get stream GUID from request.
                string path = uri.Substring(0, BasePath.Length);
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("path = " + path);
                }
#endif
                if (path != BasePath)
                {
#if DEBUG
                    if (Log.IsErrorEnabled)
                    {
                        Log.Error("Path doesn't match. Path = \"" + path + '"');
                    }
#endif

                    SendForbiddenResponse();
                    goto EndResponse;
                }

                string sid = uri.Substring(BasePath.Length, 36);
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("sid = " + sid);
                }
#endif
                string link = _spotifyLinkLookup.FindLink(sid);
                if (link == null)
                {
#if DEBUG
                    if (Log.IsErrorEnabled)
                    {
                        Log.Error("Requested stream not found. SID = " + sid);
                    }
#endif

                    SendForbiddenResponse();
                    goto EndResponse;
                }

#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Spotify Link = " + link);
                }
#endif

                // Get track object from Spotify.
                // Note: There's a timing issue which is resolved here with a retry delay.
                Track track      = null;
                int   retryCount = CreateTrackFromLinkRetryLimit;
                bool  success    = false;
                while (!success && retryCount > 0)
                {
#if DEBUG
                    if (Log.IsDebugEnabled)
                    {
                        Log.Debug("Going to try and create track instance from link. retryCount = " + retryCount);
                    }
#endif

                    Link spotifyLink = Link.Create(link);
                    track = Track.CreateFromLink(spotifyLink);
                    if (track.Duration == 0)
                    {
#if DEBUG
                        if (Log.IsErrorEnabled)
                        {
                            Log.Error("Duration is 0");
                        }
#endif
                        retryCount--;
                        Thread.Sleep(CreateTrackFromLinkRetryDelay);
                    }
                    else
                    {
                        success = true;
                    }
                }

                if (!success)
                {
                    SendForbiddenResponse();
                    goto EndResponse;
                }

                // Work out the content length.
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Duration: " + track.Duration);
                }
#endif
                int expectedSize = Program.GetExpectedBytesEncoded(track.Duration);
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Calculated size: " + expectedSize + " (" + Program.GetHumanByteString(expectedSize) + ")");
                }
#endif

                // Send the response header.
                StringBuilder headerBuild = new StringBuilder();
                headerBuild.Append("HTTP/1.1 200 OK\r\n");
                headerBuild.Append("Date: ").Append(DateTime.Now.ToUniversalTime().ToString("r")).Append("\r\n");
                headerBuild.Append("Server: SpotifyResolver\r\n");
                headerBuild.Append("Content-Type: audio/mpeg\r\n");
                headerBuild.Append("Content-Length: ").Append(expectedSize).Append("\r\n");
                headerBuild.Append("Keep-Alive: timeout=1, max=1\r\n");
                headerBuild.Append("Connection: Keep-Alive\r\n");
                headerBuild.Append("\r\n");
                byte[] headerBytes = Encoding.ASCII.GetBytes(headerBuild.ToString());
                SendToSocket(_clientSocket, headerBytes, 0, headerBytes.Length, SocketSendTimeout);

                // Start streaming the track from Spotify.
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Streaming track from Spotify.");
                }
#endif
                _spotify.StreamTrackAsync(track);

                // Wait for bytes to appear in the queue.
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Looking for bytes in the queue.");
                }
#endif
                while (SpotifyQueueEmpty())
                {
                    Thread.Sleep(30);
                }

                // Stream the bytes from Spotify via HTTP.
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Streaming audio via HTTP.");
                }
#endif
                int byteCount = 0;
                while (_runClient)
                {
                    //TODO: Ensure that the client hasn't disconnected else cancel stream from Spotify.
                    //TODO: How do we detect if a client has disconnected? This doesn't seem to work.
                    if (!_clientSocket.Connected)
                    {
#if DEBUG
                        if (Log.IsInfoEnabled)
                        {
                            Log.Info("Client disconnected. Cancelling stream from Spotify.");
                        }
#endif

                        _spotify.CancelPlayback();
                        SendForbiddenResponse();
                        goto EndResponse;
                    }

                    // Send the next packet of data.
                    IAudioData data = _spotify.LameEncoder.OutputQueue.Dequeue();
                    if (data is NullAudioData)
                    {
                        break;
                    }
                    SendToSocket(_clientSocket, ((AudioData)data).Data, 0, ((AudioData)data).Length, SocketSendTimeout);
                    byteCount += ((AudioData)data).Length;

                    // If necessary wait for more items in the queue.
                    while (_runClient && SpotifyEncoderAvailable() && SpotifyQueueEmpty())
                    {
                        Thread.Sleep(1);
                    }

                    // Quit if the encoder became unavailable.
                    if (!SpotifyEncoderAvailable())
                    {
                        break;
                    }
                }

#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Total bytes: " + byteCount + " (" + Program.GetHumanByteString(byteCount) + ")");
                }
#endif

EndResponse:
                Thread.Sleep(1); // Goto label requires something.
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Stream ending.");
                }
#endif
            }
            catch (ThreadAbortException)
            {
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Thread being aborted.");
                }
#endif
            }
            catch (Exception ex)
            {
#if DEBUG
                if (Log.IsErrorEnabled)
                {
                    Log.Error("Exception", ex);
                }
#endif
            }
            finally
            {
#if DEBUG
                if (Log.IsDebugEnabled)
                {
                    Log.Debug("Request thread ending.");
                }
#endif

                if (_clientSocket != null && _clientSocket.Connected)
                {
#if DEBUG
                    if (Log.IsDebugEnabled)
                    {
                        Log.Debug("Closing socket.");
                    }
#endif

                    _clientSocket.Shutdown(SocketShutdown.Both);
                    _clientSocket.Close();
                    _clientSocket = null;
                }

                _activeRequest = false;
            }
        }