static ServiceAction BuildAction(MethodInfo method, object service, Dictionary <string, StateVariableInfo> stateVariables) { var attributes = method.GetCustomAttributes(typeof(UpnpActionAttribute), false); if (attributes.Length != 0) { var attribute = (UpnpActionAttribute)attributes[0]; if (Omit(method.DeclaringType, service, attribute.OmitUnless)) { return(null); } var name = string.IsNullOrEmpty(attribute.Name) ? method.Name : attribute.Name; var parameters = method.GetParameters(); var arguments = new ArgumentInfo[parameters.Length]; for (var i = 0; i < parameters.Length; i++) { arguments[i] = BuildArgumentInfo(parameters[i], name, stateVariables); } var return_argument = BuildArgumentInfo(method.ReturnParameter, name, stateVariables, true); return(new ServiceAction(name, Combine(arguments, return_argument), args => { Trace(name, args.Values); var argument_array = new object[arguments.Length]; for (var i = 0; i < arguments.Length; i++) { var argument = arguments[i]; if (argument.Argument.Direction == ArgumentDirection.Out) { continue; } string value; if (args.TryGetValue(argument.Argument.Name, out value)) { var parameter_type = argument.ParameterInfo.ParameterType; if (parameter_type.IsEnum) { // TODO handle attributes foreach (var enum_value in Enum.GetValues(parameter_type)) { if (Enum.GetName(parameter_type, enum_value) == value) { argument_array[i] = enum_value; break; } } } else { argument_array[i] = Convert.ChangeType(value, parameter_type); } } else { // TODO throw } } object result; try { result = method.Invoke(service, argument_array); } catch (TargetInvocationException e) { if (e.InnerException is UpnpControlException) { throw e.InnerException; } else { throw new UpnpControlException( UpnpError.Unknown(), "Unexpected exception.", e.InnerException); } } var out_arguments = new Dictionary <string, string> (); for (var i = 0; i < arguments.Length; i++) { if (arguments[i].Argument.Direction == ArgumentDirection.In) { continue; } var value = argument_array[i]; out_arguments.Add(arguments[i].Argument.Name, value != null ? value.ToString() : ""); } if (return_argument != null) { out_arguments.Add(return_argument.Argument.Name, result.ToString()); } Trace(name, out_arguments); return out_arguments; })); } else { return(null); } }
public IMap <string, string> Invoke(string actionName, IDictionary <string, string> arguments) { var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = @"text/xml; charset=""utf-8"""; request.UserAgent = Protocol.UserAgent; request.Headers.Add("SOAPACTION", string.Format(@"""{0}#{1}""", service_type, actionName)); using (var stream = request.GetRequestStream()) { serializer.Serialize( new SoapEnvelope <Arguments> (new Arguments(service_type, actionName, arguments)), stream); } HttpWebResponse response; WebException exception; try { response = Helper.GetResponse(request); exception = null; } catch (WebException e) { response = e.Response as HttpWebResponse; if (response == null) { Log.Error(string.Format( "The request for the {0} action request on {1} failed.", actionName, url)); throw new UpnpControlException(UpnpError.Unknown(), "The invokation failed.", e); } exception = e; } using (response) { switch (response.StatusCode) { case HttpStatusCode.OK: using (var reader = XmlReader.Create(response.GetResponseStream())) { // FIXME this is a workaround for Mono bug 523151 reader.MoveToContent(); var envelope = deserializer.Deserialize <SoapEnvelope <Arguments> > (reader); if (envelope == null) { Log.Error(string.Format( "The response to the {0} action request on {1} has no envelope.", actionName, url)); throw new UpnpControlException(UpnpError.Unknown(), "The service did not provide a valid response (unable to deserialize SOAP envelope)."); } else if (envelope.Body == null) { Log.Error(string.Format( "The response to the {0} action request on {1} " + "has no envelope body.", actionName, url)); throw new UpnpControlException(UpnpError.Unknown(), "The service did not provide a valid response " + "(unable to deserialize SOAP envelope body)."); } return(new Map <string, string> (envelope.Body.Values)); } case HttpStatusCode.InternalServerError: using (var reader = XmlReader.Create(response.GetResponseStream())) { // FIME this is a workaround for Mono bug 523151 reader.MoveToContent(); var envelope = deserializer .Deserialize <SoapEnvelope <XmlShell <SoapFault <XmlShell <UpnpError> > > > > (reader); if (envelope == null) { Log.Error(string.Format( "The faulty response to the {0} action request " + "on {1} has no envelope.", actionName, url)); throw new UpnpControlException(UpnpError.Unknown(), "The invokation failed but the service did not provide valid fault information " + "(unable to deserialize SOAP envelope).", exception); } else if (envelope.Body == null) { Log.Error(string.Format( "The faulty response to the {0} action request on {1} " + "has no envelope body.", actionName, url)); throw new UpnpControlException(UpnpError.Unknown(), "The invokation failed but the service did not provide valid fault information " + "(unable to deserialize SOAP envelope body).", exception); } else if (envelope.Body.Value.Detail == null || envelope.Body.Value.Detail.Value == null) { Log.Error(string.Format( "The faulty response to the {0} action request on {1} has no UPnPError. " + @"The faultcode and faultstring are ""{2}"" and ""{3}"" respectively.", actionName, url, envelope.Body.Value.FaultCode, envelope.Body.Value.FaultString)); throw new UpnpControlException(UpnpError.Unknown(), "The invokation failed but the service did not provide valid fault information " + "(unable to deserialize a UPnPError from the SOAP envelope).", exception); } Log.Error(string.Format( "The invokation for the {0} action request on {1} failed: {2}", actionName, url, envelope.Body.Value.Detail.Value)); throw new UpnpControlException(envelope.Body.Value.Detail.Value, "The invokation failed.", exception); } default: Log.Error(string.Format( "The response to the {0} action request on {1} returned with status code {2}: {3}.", actionName, url, (int)response.StatusCode, response.StatusDescription)); throw new UpnpControlException(UpnpError.Unknown(), "The invokation failed.", exception); } } }
protected override void HandleContext(HttpListenerContext context) { base.HandleContext(context); context.Response.ContentType = @"text/xml; charset=""utf-8"""; context.Response.AddHeader("EXT", string.Empty); using (var reader = XmlReader.Create(context.Request.InputStream)) { // FIXME this is a workaround for mono bug 523151 if (reader.MoveToContent() != XmlNodeType.Element) { Log.Error(string.Format( "A control request from {0} to {1} does not have a SOAP envelope.", context.Request.RemoteEndPoint, context.Request.Url)); return; } SoapEnvelope <Arguments> requestEnvelope; try { requestEnvelope = deserializer.Deserialize <SoapEnvelope <Arguments> > (reader); } catch (Exception e) { Log.Exception(string.Format( "Failed to deserialize a control request from {0} to {1}.", context.Request.RemoteEndPoint, context.Request.Url), e); return; } if (requestEnvelope == null) { Log.Error(string.Format( "A control request from {0} to {1} does not have a valid SOAP envelope.", context.Request.RemoteEndPoint, context.Request.Url)); return; } var arguments = requestEnvelope.Body; if (arguments == null) { Log.Error(string.Format( "A control request from {0} to {1} does not have a valid argument list.", context.Request.RemoteEndPoint, context.Request.Url)); return; } if (arguments.ActionName == null) { Log.Error(string.Format( "A control request from {0} to {1} does not have an action name.", context.Request.RemoteEndPoint, context.Request.Url)); return; } ServiceAction action; try { if (actions.TryGetValue(arguments.ActionName, out action)) { Log.Information(string.Format("{0} invoked {1} on {2}.", context.Request.RemoteEndPoint, arguments.ActionName, context.Request.Url)); Arguments result; try { result = new Arguments( service_type, action.Name, action.Execute(arguments.Values), true); } catch (UpnpControlException) { throw; } catch (Exception e) { throw new UpnpControlException(UpnpError.Unknown(), "Unexpected exception.", e); } // TODO If we're allowing consumer code to subclass Argument, then we need to expose that in a // Mono.Upnp.Serializer class. We would then need to put this in a try/catch because custom // serialization code could throw. serializer.Serialize(new SoapEnvelope <Arguments> (result), context.Response.OutputStream); } else { throw new UpnpControlException(UpnpError.InvalidAction(), string.Format( "{0} attempted to invoke the non-existant action {1} on {2}.", context.Request.RemoteEndPoint, arguments.ActionName, context.Request.Url)); } } catch (UpnpControlException e) { Log.Exception(e); context.Response.StatusCode = 500; context.Response.StatusDescription = "Internal Server Error"; // TODO This needs to be a try/catch in the future too. serializer.Serialize(new SoapEnvelope <SoapFault <UpnpError> > ( new SoapFault <UpnpError> (e.UpnpError)), context.Response.OutputStream); } } }