rygel r541 - trunk/src/rygel
- From: zeeshanak svn gnome org
- To: svn-commits-list gnome org
- Subject: rygel r541 - trunk/src/rygel
- Date: Mon, 9 Feb 2009 22:27:55 +0000 (UTC)
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]