Browse Source

chore: Code refactor

krotik 4 years ago
parent
commit
3dfe9b32dc
4 changed files with 261 additions and 210 deletions
  1. 5 2
      README.md
  2. 115 0
      auth.go
  3. 0 32
      playlist.go
  4. 141 176
      requesthandler.go

+ 5 - 2
README.md

@@ -37,8 +37,11 @@ The container will have access to the current local directory and all subfolders
 
 DudelDu comes with a demo playlist. After extracting DudelDu switch to the directory `examples/demo`. Run ./run_demo.sh (Linux) or run_demo.bat (Windows) to start the server.
 
-Open a browser and view the `demo.html` in the `examples/demo` directory.
-
+Open a browser and view the `demo.html` in the `examples/demo` directory. To access the demo streams you are prompted for a username and password. The credentials are:
+```
+username: web
+password: web
+```
 You can also point your favourite audio streaming client (e.g. VLC) to the streaming URL:
 ```
 http://localhost:9091/bach/cello_suite1

+ 115 - 0
auth.go

@@ -0,0 +1,115 @@
+/*
+ * DudelDu
+ *
+ * Copyright 2016 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the MIT
+ * License, If a copy of the MIT License was not distributed with this
+ * file, You can obtain one at https://opensource.org/licenses/MIT.
+ */
+
+/*
+Package dudeldu is a simple audio streaming server using the SHOUTcast protocol.
+
+Server
+
+Server is the main server object which runs a shoutcast server instance.
+
+Using a WaitGroup a client can wait for the start and shutdown of the server.
+Incoming new connections are served with a ConnectionHandler method. The
+default implementation for this is the HandleRequest method of the
+DefaultRequestHandler object.
+
+DefaultRequestHandler
+
+DefaultRequestHandler is the default request handler implementation for the
+DudelDu server. DefaultRequestHandler has a customizable ServeRequest function.
+ServeRequest is called once a request was successfully decoded.
+
+The default implementation supports sending meta data while streaming audio. The
+metadata implementation is according to:
+
+http://www.smackfu.com/stuff/programming/shoutcast.html
+
+Playlists
+
+Playlists provide the data which is send to the client. A simple implementation
+will just read .mp3 files and send them in chunks (via the Frame() method) to
+the client.
+
+A request handler uses a PlaylistFactory to produce a Playlist for each new
+connection.
+*/
+package dudeldu
+
+import (
+	"encoding/base64"
+	"regexp"
+)
+
+/*
+requestAuthPattern is the pattern which is used to extract the request authentication
+(i case-insensitive / m multi-line mode: ^ and $ match begin/end line)
+*/
+var requestAuthPattern = regexp.MustCompile("(?im)^Authorization: Basic (\\S+).*$")
+
+/*
+checkAuth checks the authentication header of a client request.
+*/
+func (drh *DefaultRequestHandler) checkAuth(bufStr string, clientString string) (string, string, bool) {
+
+	auth := ""
+	res := requestAuthPattern.FindStringSubmatch(bufStr)
+	origBufStr, hasAuth := drh.authPeers.Get(clientString)
+
+	if len(res) > 1 {
+
+		// Decode authentication
+
+		b, err := base64.StdEncoding.DecodeString(res[1])
+		if err != nil {
+			Print("Invalid request (cannot decode authentication): ", bufStr)
+			return auth, bufStr, false
+		}
+
+		auth = string(b)
+
+		// Authorize request
+
+		if auth != drh.auth && drh.auth != "" {
+			printDebug("Wrong authentication:", auth)
+			return auth, bufStr, false
+		}
+
+		// Peer is now authorized store this so it can connect again
+
+		drh.authPeers.Put(clientString, bufStr)
+
+	} else if drh.auth != "" && !hasAuth {
+
+		// No authorization
+
+		printDebug("No authentication found")
+		return auth, bufStr, false
+
+	} else if bufStr == "" && hasAuth {
+
+		// Workaround for strange clients like VLC which send first the
+		// authentication then connect again on a different port and just
+		// expect the stream
+
+		bufStr = origBufStr.(string)
+
+		// Get again the authentication
+
+		res = requestAuthPattern.FindStringSubmatch(bufStr)
+
+		if len(res) > 1 {
+			if b, err := base64.StdEncoding.DecodeString(res[1]); err == nil {
+				auth = string(b)
+			}
+		}
+	}
+
+	return auth, bufStr, true
+}

+ 0 - 32
playlist.go

@@ -8,38 +8,6 @@
  * file, You can obtain one at https://opensource.org/licenses/MIT.
  */
 
-/*
-Package dudeldu is a simple audio streaming server using the SHOUTcast protocol.
-
-Server
-
-Server is the main server object which runs a shoutcast server instance.
-
-Using a WaitGroup a client can wait for the start and shutdown of the server.
-Incoming new connections are served with a ConnectionHandler method. The
-default implementation for this is the HandleRequest method of the
-DefaultRequestHandler object.
-
-DefaultRequestHandler
-
-DefaultRequestHandler is the default request handler implementation for the
-DudelDu server. DefaultRequestHandler has a customizable ServeRequest function.
-ServeRequest is called once a request was successfully decoded.
-
-The default implementation supports sending meta data while streaming audio. The
-metadata implementation is according to:
-
-http://www.smackfu.com/stuff/programming/shoutcast.html
-
-Playlists
-
-Playlists provide the data which is send to the client. A simple implementation
-will just read .mp3 files and send them in chunks (via the Frame() method) to
-the client.
-
-A request handler uses a PlaylistFactory to produce a Playlist for each new
-connection.
-*/
 package dudeldu
 
 import "errors"

+ 141 - 176
requesthandler.go

@@ -12,7 +12,6 @@ package dudeldu
 
 import (
 	"bytes"
-	"encoding/base64"
 	"fmt"
 	"io"
 	"log"
@@ -60,12 +59,6 @@ requestOffsetPattern is the pattern which is used to extract the requested offse
 */
 var requestOffsetPattern = regexp.MustCompile("(?im)^Range: bytes=([0-9]+)-.*$")
 
-/*
-requestAuthPattern is the pattern which is used to extract the request authentication
-(i case-insensitive / m multi-line mode: ^ and $ match begin/end line)
-*/
-var requestAuthPattern = regexp.MustCompile("(?im)^Authorization: Basic (\\S+).*$")
-
 /*
 Print logger method. Using a custom type so it can be customized.
 */
@@ -114,9 +107,7 @@ finishes.
 */
 func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
 
-	if DebugOutput {
-		Print("Handling request from: ", c.RemoteAddr())
-	}
+	printDebug("Handling request from: ", c.RemoteAddr())
 
 	defer func() {
 		c.Close()
@@ -129,32 +120,10 @@ func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
 		return
 	}
 
-	rbuf := make([]byte, 512, 512)
-	var buf bytes.Buffer
-
-	// Decode request
-
-	n, err := c.Read(rbuf)
-
-	for n > 0 || (err != nil && err != io.EOF) {
-
-		// Do some error checking
-
-		if err != nil {
-			Print(err)
-			return
-		} else if buf.Len() > MaxRequestSize {
-			Print("Illegal request: Request is too long")
-			return
-		}
-
-		buf.Write(rbuf[:n])
-
-		if strings.Contains(string(rbuf), "\r\n\r\n") {
-			break
-		}
-
-		n, err = c.Read(rbuf)
+	buf, err := drh.decodeRequestHeader(c)
+	if err != nil {
+		Print(err)
+		return
 	}
 
 	// Add ending sequence in case the client "forgets"
@@ -168,76 +137,19 @@ func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
 		clientString, _, _ = net.SplitHostPort(c.RemoteAddr().String())
 	}
 
-	if DebugOutput {
-		Print("Client:", c.RemoteAddr(), " Request:", bufStr)
-	}
+	printDebug("Client:", c.RemoteAddr(), " Request:", bufStr)
 
 	if i := strings.Index(bufStr, "\r\n\r\n"); i >= 0 {
+		var auth string
+		var ok bool
+
 		bufStr = strings.TrimSpace(bufStr[:i])
 
 		// Check authentication
 
-		auth := ""
-		res := requestAuthPattern.FindStringSubmatch(bufStr)
-		origBufStr, hasAuth := drh.authPeers.Get(clientString)
-
-		if len(res) > 1 {
-
-			// Decode authentication
-
-			b, err := base64.StdEncoding.DecodeString(res[1])
-			if err != nil {
-				drh.writeUnauthorized(c)
-				Print("Invalid request (cannot decode authentication): ", bufStr)
-				return
-			}
-
-			auth = string(b)
-
-			// Authorize request
-
-			if auth != drh.auth && drh.auth != "" {
-
-				if DebugOutput {
-					Print("Wrong authentication:", string(b))
-				}
-
-				drh.writeUnauthorized(c)
-				return
-			}
-
-			// Peer is now authorized store this so it can connect again
-
-			drh.authPeers.Put(clientString, bufStr)
-
-		} else if drh.auth != "" && !hasAuth {
-
-			// No authorization
-
-			if DebugOutput {
-				Print("No authentication found")
-			}
-
+		if auth, bufStr, ok = drh.checkAuth(bufStr, clientString); !ok {
 			drh.writeUnauthorized(c)
 			return
-
-		} else if bufStr == "" && hasAuth {
-
-			// Workaround for strange clients like VLC which send first the
-			// authentication then connect again on a different port and just
-			// expect the stream
-
-			bufStr = origBufStr.(string)
-
-			// Get again the authentication
-
-			res = requestAuthPattern.FindStringSubmatch(bufStr)
-
-			if len(res) > 1 {
-				if b, err := base64.StdEncoding.DecodeString(res[1]); err == nil {
-					auth = string(b)
-				}
-			}
 		}
 
 		// Check if the client supports meta data
@@ -251,7 +163,7 @@ func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
 		// Extract offset
 
 		offset := 0
-		res = requestOffsetPattern.FindStringSubmatch(bufStr)
+		res := requestOffsetPattern.FindStringSubmatch(bufStr)
 
 		if len(res) > 1 {
 
@@ -277,15 +189,49 @@ func (drh *DefaultRequestHandler) HandleRequest(c net.Conn, nerr net.Error) {
 	Print("Invalid request: ", bufStr)
 }
 
+/*
+decodeRequestHeader decodes the header of an incoming request.
+*/
+func (drh *DefaultRequestHandler) decodeRequestHeader(c net.Conn) (*bytes.Buffer, error) {
+	var buf bytes.Buffer
+
+	rbuf := make([]byte, 512, 512)
+
+	// Decode request
+
+	n, err := c.Read(rbuf)
+
+	for n > 0 || (err != nil && err != io.EOF) {
+
+		// Do some error checking
+
+		if err != nil {
+			return nil, err
+		} else if buf.Len() > MaxRequestSize {
+			return nil, fmt.Errorf("Illegal request: Request is too long")
+		}
+
+		buf.Write(rbuf[:n])
+
+		if strings.Contains(string(rbuf), "\r\n\r\n") {
+			break
+		}
+
+		n, err = c.Read(rbuf)
+	}
+
+	return &buf, nil
+}
+
 /*
 defaultServeRequest is called once a request was successfully decoded.
 */
 func (drh *DefaultRequestHandler) defaultServeRequest(c net.Conn, path string, metaDataSupport bool, offset int, auth string) {
+	var writtenBytes uint64
+	var currentPlaying string
 	var err error
 
-	if DebugOutput {
-		Print("Serve request path:", path, " Metadata support:", metaDataSupport, " Offset:", offset)
-	}
+	printDebug("Serve request path:", path, " Metadata support:", metaDataSupport, " Offset:", offset)
 
 	pl := drh.PlaylistFactory.Playlist(path, drh.shuffle)
 	if pl == nil {
@@ -298,13 +244,9 @@ func (drh *DefaultRequestHandler) defaultServeRequest(c net.Conn, path string, m
 
 	err = drh.writeStreamStartResponse(c, pl.Name(), pl.ContentType(), metaDataSupport)
 
-	clientWritten := 0
-	var writtenBytes uint64
-	currentPlaying := ""
 	frameOffset := offset
 
 	for {
-
 		for !pl.Finished() {
 
 			if DebugOutput {
@@ -312,8 +254,8 @@ func (drh *DefaultRequestHandler) defaultServeRequest(c net.Conn, path string, m
 
 				if playingString != currentPlaying {
 					currentPlaying = playingString
-					Print("Written bytes: ", writtenBytes)
-					Print("Sending: ", currentPlaying)
+					printDebug("Written bytes: ", writtenBytes)
+					printDebug("Sending: ", currentPlaying)
 				}
 			}
 
@@ -324,107 +266,121 @@ func (drh *DefaultRequestHandler) defaultServeRequest(c net.Conn, path string, m
 				return
 			}
 
-			frame, err := pl.Frame()
+			frameOffset, writtenBytes, err = drh.writeFrame(c, pl, frameOffset,
+				writtenBytes, metaDataSupport)
+		}
 
-			// Handle offsets
+		// Handle looping - do not loop if close returns an error
 
-			if frameOffset > 0 && err == nil {
+		if pl.Close() != nil || !drh.loop {
+			break
+		} else if drh.LoopTimes != -1 {
+			drh.LoopTimes--
+			if drh.LoopTimes == 0 {
+				break
+			}
+		}
+	}
 
-				for frameOffset > len(frame) && err == nil {
-					frameOffset -= len(frame)
-					frame, err = pl.Frame()
-				}
+	printDebug("Serve request path:", path, " complete")
+}
 
-				if err == nil {
-					frame = frame[frameOffset:]
-					frameOffset = 0
+/*
+writeFrame writes a frame to a client.
+*/
+func (drh *DefaultRequestHandler) writeFrame(c net.Conn, pl Playlist, frameOffset int,
+	writtenBytes uint64, metaDataSupport bool) (int, uint64, error) {
 
-					if len(frame) == 0 {
-						frame, err = pl.Frame()
-					}
-				}
-			}
+	frame, err := pl.Frame()
 
-			if frame == nil {
-				if !pl.Finished() {
-					Print(fmt.Sprintf("Empty frame for: %v - %v (Error: %v)", pl.Title(), pl.Artist(), err))
-				}
-				continue
-			} else if err != nil {
-				if err != ErrPlaylistEnd {
-					Print(fmt.Sprintf("Error while retrieving playlist data: %v", err))
-				}
-				err = nil
-			}
+	// Handle offsets
 
-			// Check if meta data should be send
+	if frameOffset > 0 && err == nil {
 
-			if metaDataSupport && writtenBytes+uint64(len(frame)) >= MetaDataInterval {
+		for frameOffset > len(frame) && err == nil {
+			frameOffset -= len(frame)
+			frame, err = pl.Frame()
+		}
 
-				// Write rest data before sending meta data
+		if err == nil {
+			frame = frame[frameOffset:]
+			frameOffset = 0
 
-				preMetaDataLength := MetaDataInterval - writtenBytes
-				if preMetaDataLength > 0 {
-					if err == nil {
+			if len(frame) == 0 {
+				frame, err = pl.Frame()
+			}
+		}
+	}
 
-						_, err = c.Write(frame[:preMetaDataLength])
+	if frame == nil {
 
-						frame = frame[preMetaDataLength:]
-						writtenBytes += preMetaDataLength
-					}
-				}
+		if !pl.Finished() {
+			Print(fmt.Sprintf("Empty frame for: %v - %v (Error: %v)", pl.Title(), pl.Artist(), err))
+		}
 
-				if err == nil {
+		return frameOffset, writtenBytes, err
 
-					// Write meta data - no error checking (next write should fail)
+	} else if err != nil {
 
-					drh.writeStreamMetaData(c, pl)
+		if err != ErrPlaylistEnd {
+			Print(fmt.Sprintf("Error while retrieving playlist data: %v", err))
+		}
 
-					// Write rest of the frame
+		err = nil
+	}
 
-					c.Write(frame)
-					writtenBytes += uint64(len(frame))
-				}
+	// Check if meta data should be send
 
-				writtenBytes -= MetaDataInterval
+	if metaDataSupport && writtenBytes+uint64(len(frame)) >= MetaDataInterval {
 
-			} else {
+		// Write rest data before sending meta data
 
-				// Just write the frame to the client
+		if preMetaDataLength := MetaDataInterval - writtenBytes; preMetaDataLength > 0 {
+			if err == nil {
 
-				if err == nil {
+				_, err = c.Write(frame[:preMetaDataLength])
 
-					clientWritten, _ = c.Write(frame)
+				frame = frame[preMetaDataLength:]
+				writtenBytes += preMetaDataLength
+			}
+		}
 
-					// Abort if the client does not accept more data
+		if err == nil {
 
-					if clientWritten == 0 && len(frame) > 0 {
-						Print(fmt.Sprintf("Could not write to client - closing connection"))
-						return
-					}
-				}
+			// Write meta data - no error checking (next write should fail)
 
-				pl.ReleaseFrame(frame)
+			drh.writeStreamMetaData(c, pl)
 
-				writtenBytes += uint64(len(frame))
-			}
+			// Write rest of the frame
+
+			c.Write(frame)
+			writtenBytes += uint64(len(frame))
 		}
 
-		// Handle looping - do not loop if close returns an error
+		writtenBytes -= MetaDataInterval
 
-		if pl.Close() != nil || !drh.loop {
-			break
-		} else if drh.LoopTimes != -1 {
-			drh.LoopTimes--
-			if drh.LoopTimes == 0 {
-				break
+	} else {
+
+		// Just write the frame to the client
+
+		if err == nil {
+
+			clientWritten, _ := c.Write(frame)
+
+			// Abort if the client does not accept more data
+
+			if clientWritten == 0 && len(frame) > 0 {
+				return frameOffset, writtenBytes,
+					fmt.Errorf("Could not write to client - closing connection")
 			}
 		}
-	}
 
-	if DebugOutput {
-		Print("Serve request path:", path, " complete")
+		pl.ReleaseFrame(frame)
+
+		writtenBytes += uint64(len(frame))
 	}
+
+	return frameOffset, writtenBytes, err
 }
 
 /*
@@ -490,3 +446,12 @@ func (drh *DefaultRequestHandler) writeUnauthorized(c net.Conn) error {
 
 	return err
 }
+
+/*
+debug will print additional debug output if `DebugOutput` is enabled.
+*/
+func printDebug(v ...interface{}) {
+	if DebugOutput {
+		Print(v...)
+	}
+}