rygel r541 - trunk/src/rygel



Author: zeeshanak
Date: Mon Feb  9 22:27:54 2009
New Revision: 541
URL: http://svn.gnome.org/viewvc/rygel?rev=541&view=rev

Log:
Put HTTP request handling into a separate class: HTTPRequest.

This will allow us to make async calls during the handling of HTTP
requests.

Added:
   trunk/src/rygel/rygel-http-request.vala
Modified:
   trunk/src/rygel/Makefile.am
   trunk/src/rygel/rygel-http-server.vala

Modified: trunk/src/rygel/Makefile.am
==============================================================================
--- trunk/src/rygel/Makefile.am	(original)
+++ trunk/src/rygel/Makefile.am	Mon Feb  9 22:27:54 2009
@@ -42,6 +42,8 @@
 		rygel-plugin-loader.c \
                 rygel-http-server.c \
                 rygel-http-server.h \
+		rygel-http-request.c \
+		rygel-http-request.h \
                 rygel-http-response.c \
                 rygel-http-response.h \
                 rygel-live-response.c \
@@ -88,6 +90,8 @@
 		rygel-plugin-loader.vala \
 		rygel-http-server.c \
 		rygel-http-server.h \
+		rygel-http-request.c \
+		rygel-http-request.h \
 		rygel-http-response.c \
 		rygel-http-response.h \
 		rygel-live-response.c \
@@ -130,6 +134,7 @@
 		    rygel-connection-manager.vala \
 		    rygel-media-receiver-registrar.vala \
 		    rygel-http-server.vala \
+		    rygel-http-request.vala \
 		    rygel-http-response.vala \
 		    rygel-live-response.vala \
 		    rygel-seekable-response.vala \

Added: trunk/src/rygel/rygel-http-request.vala
==============================================================================
--- (empty file)
+++ trunk/src/rygel/rygel-http-request.vala	Mon Feb  9 22:27:54 2009
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2008, 2009 Nokia Corporation, all rights reserved.
+ * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *         Jorn Baayen <jorn baayen gmail com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Rygel;
+using Gst;
+
+public errordomain Rygel.HTTPRequestError {
+    UNACCEPTABLE = Soup.KnownStatusCode.NOT_ACCEPTABLE,
+    INVALID_RANGE = Soup.KnownStatusCode.BAD_REQUEST,
+    OUT_OF_RANGE = Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE,
+    BAD_REQUEST = Soup.KnownStatusCode.BAD_REQUEST,
+    NOT_FOUND = Soup.KnownStatusCode.NOT_FOUND
+}
+
+/**
+ * Responsible for handling HTTP client requests.
+ */
+public class Rygel.HTTPRequest : GLib.Object {
+    private unowned ContentDirectory content_dir;
+    private Soup.Server server;
+    private Soup.Message msg;
+    private HashTable<string,string>? query;
+
+    private HTTPResponse response;
+
+    public signal void handled ();
+
+    private string item_id;
+    private MediaItem item;
+    private Seek seek;
+
+    public HTTPRequest (ContentDirectory          content_dir,
+                        Soup.Server               server,
+                        Soup.Message              msg,
+                        HashTable<string,string>? query) {
+        this.content_dir = content_dir;
+        this.server = server;
+        this.msg = msg;
+        this.query = query;
+    }
+
+    public void start_processing () {
+        if (this.msg.method != "HEAD" && this.msg.method != "GET") {
+            /* We only entertain 'HEAD' and 'GET' requests */
+            this.handle_error (
+                        new HTTPRequestError.BAD_REQUEST ("Invalid Request"));
+            return;
+        }
+
+        if (query != null) {
+            this.item_id = query.lookup ("itemid");
+        }
+
+        if (this.item_id == null) {
+            this.handle_error (new HTTPRequestError.NOT_FOUND ("Not Found"));
+            return;
+        }
+
+        this.handle_item_request ();
+    }
+
+    private void stream_from_gst_source (Element# src) throws Error {
+        var response = new LiveResponse (this.server,
+                                         this.msg,
+                                         "RygelLiveResponse",
+                                         src);
+        this.response = response;
+
+        response.start ();
+        response.ended += on_response_ended;
+    }
+
+    private void serve_uri (string uri, size_t size) {
+        var response = new SeekableResponse (this.server,
+                                             this.msg,
+                                             uri,
+                                             this.seek,
+                                             size);
+        this.response = response;
+
+        response.ended += on_response_ended;
+    }
+
+    private void on_response_ended (HTTPResponse response) {
+        this.end (Soup.KnownStatusCode.NONE);
+    }
+
+    private void handle_item_request () {
+        // Fetch the requested item
+        this.fetch_requested_item ();
+        if (this.item == null) {
+            return;
+        }
+
+        try {
+            this.parse_range ();
+        } catch (Error error) {
+            this.handle_error (error);
+            return;
+        }
+
+        // Add headers
+        this.add_item_headers ();
+
+        if (this.msg.method == "HEAD") {
+            // Only headers requested, no need to send contents
+            this.end (Soup.KnownStatusCode.OK);
+            return;
+        }
+
+        if (this.item.size > 0) {
+            this.handle_interactive_item ();
+        } else {
+            this.handle_streaming_item ();
+        }
+    }
+
+    private void add_item_headers () {
+        if (this.item.mime_type != null) {
+            this.msg.response_headers.append ("Content-Type",
+                                              this.item.mime_type);
+        }
+
+        if (this.item.size >= 0) {
+            this.msg.response_headers.append ("Content-Length",
+                                              this.item.size.to_string ());
+        }
+
+        if (this.item.size > 0) {
+            int64 first_byte;
+            int64 last_byte;
+
+            if (this.seek != null) {
+                first_byte = this.seek.start;
+                last_byte = this.seek.stop;
+            } else {
+                first_byte = 0;
+                last_byte = this.item.size - 1;
+            }
+
+            // Content-Range: bytes START_BYTE-STOP_BYTE/TOTAL_LENGTH
+            var content_range = "bytes " +
+                                first_byte.to_string () + "-" +
+                                last_byte.to_string () + "/" +
+                                this.item.size.to_string ();
+            this.msg.response_headers.append ("Content-Range", content_range);
+            this.msg.response_headers.append ("Accept-Ranges", "bytes");
+        }
+    }
+
+    private void handle_streaming_item () {
+        string uri = this.item.uri;
+        dynamic Element src = null;
+
+        if (uri != null) {
+            // URI provided, try to create source element from it
+            src = Element.make_from_uri (URIType.SRC, uri, null);
+        } else {
+            // No URI provided, ask for source element
+            src = this.item.create_stream_source ();
+        }
+
+        if (src == null) {
+            this.handle_error (new HTTPRequestError.NOT_FOUND ("Not Found"));
+            return;
+        }
+
+        // For rtspsrc since some RTSP sources takes a while to start
+        // transmitting
+        src.tcp_timeout = (int64) 60000000;
+
+        try {
+            // Then start the gst stream
+            this.stream_from_gst_source (src);
+        } catch (Error error) {
+            this.handle_error (error);
+            return;
+        }
+    }
+
+    private void handle_interactive_item () {
+        string uri = this.item.uri;
+
+        if (uri == null) {
+            var error = new HTTPRequestError.NOT_FOUND (
+                                "Requested item '%s' didn't provide a URI\n",
+                                this.item.id);
+            this.handle_error (error);
+            return;
+        }
+
+        this.serve_uri (uri, this.item.size);
+    }
+
+    private void parse_range () throws HTTPRequestError {
+            string range;
+            string[] range_tokens;
+
+            range = this.msg.request_headers.get ("Range");
+            if (range == null) {
+                return;
+            }
+
+            // We have a Range header. Parse.
+            if (!range.has_prefix ("bytes=")) {
+                throw new HTTPRequestError.INVALID_RANGE ("Invalid Range '%s'",
+                                                       range);
+            }
+
+            range_tokens = range.offset (6).split ("-", 2);
+
+            if (range_tokens[0] == null || range_tokens[1] == null) {
+                throw new HTTPRequestError.INVALID_RANGE ("Invalid Range '%s'",
+                                                       range);
+            }
+
+            this.seek = new Seek (Format.BYTES, 0, this.item.size - 1);
+
+            // Get first byte position
+            string first_byte = range_tokens[0];
+            if (first_byte[0].isdigit ()) {
+                this.seek.start = first_byte.to_int64 ();
+            } else if (first_byte  != "") {
+                throw new HTTPRequestError.INVALID_RANGE ("Invalid Range '%s'",
+                                                       range);
+            }
+
+            // Get last byte position if specified
+            string last_byte = range_tokens[1];
+            if (last_byte[0].isdigit ()) {
+                this.seek.stop = last_byte.to_int64 ();
+            } else if (last_byte  != "") {
+                throw new HTTPRequestError.INVALID_RANGE ("Invalid Range '%s'",
+                                                       range);
+            }
+
+            if (this.item.size > 0) {
+                // shouldn't go beyond actual length of media
+                if (this.seek.start > this.item.size ||
+                    this.seek.length > this.item.size) {
+                    throw new HTTPRequestError.OUT_OF_RANGE (
+                            "Range '%s' not setsifiable", range);
+                }
+
+                // No need to seek if whole stream is requested
+                if (this.seek.start == 0 &&
+                    this.seek.length == this.item.size) {
+                    this.seek == null;
+                    return;
+                }
+            } else if (this.seek.start == 0) {
+                // Might be an attempt to get the size, in which case it's not
+                // an error. Just don't seek.
+                this.seek == null;
+                return;
+            } else {
+                throw new HTTPRequestError.UNACCEPTABLE (
+                            "Partial download not applicable for item %s",
+                            this.item.id);
+            }
+    }
+
+    private void fetch_requested_item () {
+        MediaObject media_object;
+
+        try {
+            media_object = this.content_dir.find_object_by_id (this.item_id);
+        } catch (Error error) {
+            this.handle_error (error);
+            return;
+        }
+
+        if (media_object == null || !(media_object is MediaItem)) {
+            this.handle_error (new HTTPRequestError.NOT_FOUND (
+                                        "requested item '%s' not found",
+                                        this.item_id));
+            return;
+        }
+
+        this.item = (MediaItem) media_object;
+    }
+
+    private void handle_error (Error error) {
+        warning ("%s", error.message);
+
+        uint status;
+        if (error is HTTPRequestError) {
+            status = error.code;
+        } else {
+            status = Soup.KnownStatusCode.NOT_FOUND;
+        }
+
+        this.end (status);
+    }
+
+    public void end (uint status) {
+        if (status != Soup.KnownStatusCode.NONE) {
+            this.msg.set_status (status);
+        }
+
+        this.handled ();
+    }
+}
+

Modified: trunk/src/rygel/rygel-http-server.vala
==============================================================================
--- trunk/src/rygel/rygel-http-server.vala	(original)
+++ trunk/src/rygel/rygel-http-server.vala	Mon Feb  9 22:27:54 2009
@@ -1,10 +1,8 @@
 /*
  * Copyright (C) 2008, 2009 Nokia Corporation, all rights reserved.
- * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
  *                               <zeeshan ali nokia com>
- *         Jorn Baayen <jorn baayen gmail com>
  *
  * This file is part of Rygel.
  *
@@ -28,12 +26,6 @@
 using GUPnP;
 using Gee;
 
-public errordomain Rygel.HTTPServerError {
-    UNACCEPTABLE = Soup.KnownStatusCode.NOT_ACCEPTABLE,
-    INVALID_RANGE = Soup.KnownStatusCode.BAD_REQUEST,
-    OUT_OF_RANGE = Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE
-}
-
 public class Rygel.HTTPServer : GLib.Object {
     private const string SERVER_PATH_PREFIX = "/RygelHTTPServer";
     private string path_root;
@@ -41,13 +33,13 @@
     // Reference to associated ContentDirectory service
     private unowned ContentDirectory content_dir;
     private GUPnP.Context context;
-    private ArrayList<HTTPResponse> responses;
+    private ArrayList<HTTPRequest> requests;
 
     public HTTPServer (ContentDirectory content_dir,
                        string           name) {
         this.content_dir = content_dir;
         this.context = content_dir.context;
-        this.responses = new ArrayList<HTTPResponse> ();
+        this.requests = new ArrayList<HTTPRequest> ();
 
         this.path_root = SERVER_PATH_PREFIX + "/" + name;
 
@@ -72,35 +64,9 @@
         return create_uri_for_path (query);
     }
 
-    private void stream_from_gst_source (Element#     src,
-                                         Soup.Message msg) throws Error {
-        var response = new LiveResponse (this.context.server,
-                                         msg,
-                                         "RygelLiveResponse",
-                                         src);
-        response.start ();
-        response.ended += on_response_ended;
-
-        this.responses.add (response);
-    }
-
-    private void serve_uri (string       uri,
-                            Soup.Message msg,
-                            Seek?        seek,
-                            size_t       size) throws Error {
-        var response = new SeekableResponse (this.context.server,
-                                             msg,
-                                             uri,
-                                             seek,
-                                             size);
-        response.ended += on_response_ended;
-
-        this.responses.add (response);
-    }
-
-    private void on_response_ended (HTTPResponse response) {
-        /* Remove the response from our list. */
-        this.responses.remove (response);
+    private void on_request_handled (HTTPRequest request) {
+        /* Remove the request from our list. */
+        this.requests.remove (request);
     }
 
     private void server_handler (Soup.Server               server,
@@ -108,244 +74,12 @@
                                  string                    server_path,
                                  HashTable<string,string>? query,
                                  Soup.ClientContext        soup_client) {
-        if (msg.method != "HEAD" && msg.method != "GET") {
-            /* We only entertain 'HEAD' and 'GET' requests */
-            msg.set_status (Soup.KnownStatusCode.BAD_REQUEST);
-            return;
-        }
-
-        string item_id = null;
-        if (query != null) {
-            item_id = query.lookup ("itemid");
-        }
-
-        if (item_id == null) {
-            msg.set_status (Soup.KnownStatusCode.NOT_FOUND);
-            return;
-        }
-
-        this.handle_item_request (msg, item_id);
-    }
-
-    private void handle_item_request (Soup.Message msg,
-                                      string       item_id) {
-        MediaItem item;
-
-        // Fetch the requested item
-        item = this.get_requested_item (item_id);
-        if (item == null) {
-            msg.set_status (Soup.KnownStatusCode.NOT_FOUND);
-            return;
-        }
-
-        Seek seek = null;
-
-        try {
-            seek = this.parse_range (msg, item);
-        } catch (HTTPServerError err) {
-            warning ("%s", err.message);
-            msg.set_status (err.code);
-            return;
-        }
-
-        // Add headers
-        this.add_item_headers (msg, item, seek);
-
-        if (msg.method == "HEAD") {
-            // Only headers requested, no need to send contents
-            msg.set_status (Soup.KnownStatusCode.OK);
-            return;
-        }
-
-        if (item.size > 0) {
-            this.handle_interactive_item (msg, item, seek);
-        } else {
-            this.handle_streaming_item (msg, item);
-        }
-    }
-
-    private void add_item_headers (Soup.Message msg,
-                                   MediaItem    item,
-                                   Seek?        seek) {
-        if (item.mime_type != null) {
-            msg.response_headers.append ("Content-Type", item.mime_type);
-        }
-
-        if (item.size >= 0) {
-            msg.response_headers.append ("Content-Length",
-                                         item.size.to_string ());
-        }
-
-        if (item.size > 0) {
-            int64 first_byte;
-            int64 last_byte;
-
-            if (seek != null) {
-                first_byte = seek.start;
-                last_byte = seek.stop;
-            } else {
-                first_byte = 0;
-                last_byte = item.size - 1;
-            }
-
-            // Content-Range: bytes START_BYTE-STOP_BYTE/TOTAL_LENGTH
-            var content_range = "bytes " +
-                                first_byte.to_string () + "-" +
-                                last_byte.to_string () + "/" +
-                                item.size.to_string ();
-            msg.response_headers.append ("Content-Range", content_range);
-            msg.response_headers.append ("Accept-Ranges", "bytes");
-        }
-    }
-
-    private void handle_streaming_item (Soup.Message msg,
-                                        MediaItem    item) {
-        string uri = item.uri;
-        dynamic Element src = null;
-
-        if (uri != null) {
-            // URI provided, try to create source element from it
-            src = Element.make_from_uri (URIType.SRC, uri, null);
-        } else {
-            // No URI provided, ask for source element
-            src = item.create_stream_source ();
-        }
-
-        if (src == null) {
-            warning ("Failed to create source element for item: %s\n",
-                     item.id);
-            msg.set_status (Soup.KnownStatusCode.NOT_FOUND);
-            return;
-        }
-
-        // For rtspsrc since some RTSP sources takes a while to start
-        // transmitting
-        src.tcp_timeout = (int64) 60000000;
-
-        try {
-            // Then start the gst stream
-            this.stream_from_gst_source (src, msg);
-        } catch (Error error) {
-            critical ("Error in attempting to start streaming %s: %s",
-                      uri,
-                      error.message);
-        }
-    }
-
-    private void handle_interactive_item (Soup.Message msg,
-                                          MediaItem    item,
-                                          Seek?        seek) {
-        string uri = item.uri;
-
-        if (uri == null) {
-            warning ("Requested item '%s' didn't provide a URI\n", item.id);
-            msg.set_status (Soup.KnownStatusCode.NOT_FOUND);
-            return;
-        }
-
-        try {
-            this.serve_uri (uri, msg, seek, item.size);
-        } catch (Error error) {
-            warning ("Error in attempting to serve %s: %s",
-                     uri,
-                     error.message);
-            msg.set_status (Soup.KnownStatusCode.NOT_FOUND);
-        }
-    }
-
-    /* Parses the HTTP Range header on @message and sets:
-     *
-     * @offset to the requested offset (left unchanged if none specified),
-     * @length to the requested length (left unchanged if none specified).
-     *
-     * Both @offset and @length are expected to be initialised to their default
-     * values. Throws a #HTTPServerError in case of error.
-     *
-     * Returns %true a range header was found, false otherwise. */
-    private Seek? parse_range (Soup.Message message,
-                                MediaItem   item)
-                                throws      HTTPServerError {
-            string range;
-            string[] range_tokens;
-            Seek seek = null;
-
-            range = message.request_headers.get ("Range");
-            if (range == null) {
-                return seek;
-            }
-
-            // We have a Range header. Parse.
-            if (!range.has_prefix ("bytes=")) {
-                throw new HTTPServerError.INVALID_RANGE ("Invalid Range '%s'",
-                                                       range);
-            }
-
-            range_tokens = range.offset (6).split ("-", 2);
-
-            if (range_tokens[0] == null || range_tokens[1] == null) {
-                throw new HTTPServerError.INVALID_RANGE ("Invalid Range '%s'",
-                                                       range);
-            }
-
-            seek = new Seek (Format.BYTES, 0, item.size - 1);
-
-            // Get first byte position
-            string first_byte = range_tokens[0];
-            if (first_byte[0].isdigit ()) {
-                seek.start = first_byte.to_int64 ();
-            } else if (first_byte  != "") {
-                throw new HTTPServerError.INVALID_RANGE ("Invalid Range '%s'",
-                                                       range);
-            }
-
-            // Get last byte position if specified
-            string last_byte = range_tokens[1];
-            if (last_byte[0].isdigit ()) {
-                seek.stop = last_byte.to_int64 ();
-            } else if (last_byte  != "") {
-                throw new HTTPServerError.INVALID_RANGE ("Invalid Range '%s'",
-                                                       range);
-            }
-
-            if (item.size > 0) {
-                // shouldn't go beyond actual length of media
-                if (seek.start > item.size ||
-                    seek.length > item.size) {
-                    throw new HTTPServerError.OUT_OF_RANGE (
-                            "Range '%s' not setsifiable", range);
-                }
-
-                // No need to seek if whole stream is requested
-                if (seek.start == 0 && seek.length == item.size) {
-                    return null;
-                }
-            } else if (seek.start == 0) {
-                // Might be an attempt to get the size, in which case it's not
-                // an error. Just don't seek.
-                return null;
-            } else {
-                throw new HTTPServerError.UNACCEPTABLE (
-                            "Partial download not applicable for item %s",
-                            item.id);
-            }
-
-            return seek;
-    }
+        var request = new HTTPRequest (this.content_dir, server, msg, query);
 
-    private MediaItem? get_requested_item (string item_id) {
-        MediaObject media_object;
+        request.handled += this.on_request_handled;
+        this.requests.add (request);
 
-        try {
-            media_object = this.content_dir.find_object_by_id (item_id);
-        } catch (Error err) {
-            return null;
-        }
-
-        if (media_object != null && media_object is MediaItem) {
-            return (MediaItem) media_object;
-        } else {
-            return null;
-        }
+        request.start_processing ();
     }
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]