Xamarin Public Jenkins (auto-signing) 94b2861243 Imported Upstream version 4.8.0.309
Former-commit-id: 5f9c6ae75f295e057a7d2971f3a6df4656fa8850
2016-11-10 13:04:39 +00:00

685 lines
22 KiB
C#

//
// WebMessageFormatter.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
// Atsushi Enomoto <atsushi@xamarin.com>
//
// Copyright (C) 2008,2009 Novell, Inc (http://www.novell.com)
// Copyright (C) 2011 Xamarin, Inc (http://xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.Text;
using System.Xml;
#if MOBILE
using XmlObjectSerializer = System.Object;
#endif
namespace System.ServiceModel.Dispatcher
{
// This set of classes is to work as message formatters for
// WebHttpBehavior. There are couple of aspects to differentiate
// implementations:
// - request/reply and client/server
// by WebMessageFormatter hierarchy
// - WebClientMessageFormatter - for client
// - RequestClientFormatter - for request
// - ReplyClientFormatter - for response
// - WebDispatchMessageFormatter - for server
// - RequestDispatchFormatter - for request
// - ReplyDispatchFormatter - for response
//
// FIXME: below items need more work
// - HTTP method differences
// - GET (WebGet)
// - POST (other way)
// - output format: Stream, JSON, XML ...
internal abstract class WebMessageFormatter
{
OperationDescription operation;
ServiceEndpoint endpoint;
QueryStringConverter converter;
WebHttpBehavior behavior;
UriTemplate template;
WebAttributeInfo info = null;
public WebMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
{
this.operation = operation;
this.endpoint = endpoint;
this.converter = converter;
this.behavior = behavior;
ApplyWebAttribute ();
#if !MOBILE
// This is a hack for WebScriptEnablingBehavior
var jqc = converter as JsonQueryStringConverter;
if (jqc != null)
BodyName = jqc.CustomWrapperName;
#endif
}
void ApplyWebAttribute ()
{
MethodInfo mi = operation.SyncMethod ?? operation.BeginMethod;
object [] atts = mi.GetCustomAttributes (typeof (WebGetAttribute), false);
if (atts.Length > 0)
info = ((WebGetAttribute) atts [0]).Info;
atts = mi.GetCustomAttributes (typeof (WebInvokeAttribute), false);
if (atts.Length > 0)
info = ((WebInvokeAttribute) atts [0]).Info;
if (info == null)
info = new WebAttributeInfo ();
template = info.BuildUriTemplate (Operation, GetMessageDescription (MessageDirection.Input));
}
public string BodyName { get; set; }
public WebHttpBehavior Behavior {
get { return behavior; }
}
public WebAttributeInfo Info {
get { return info; }
}
public WebMessageBodyStyle BodyStyle {
get { return info.IsBodyStyleSetExplicitly ? info.BodyStyle : behavior.DefaultBodyStyle; }
}
public bool IsRequestBodyWrapped {
get {
switch (BodyStyle) {
case WebMessageBodyStyle.Wrapped:
case WebMessageBodyStyle.WrappedRequest:
return true;
}
return BodyName != null;
}
}
public bool IsResponseBodyWrapped {
get {
switch (BodyStyle) {
case WebMessageBodyStyle.Wrapped:
case WebMessageBodyStyle.WrappedResponse:
return true;
}
return BodyName != null;
}
}
public OperationDescription Operation {
get { return operation; }
}
public QueryStringConverter Converter {
get { return converter; }
}
public ServiceEndpoint Endpoint {
get { return endpoint; }
}
public UriTemplate UriTemplate {
get { return template; }
}
protected WebContentFormat ToContentFormat (WebMessageFormat src, object result)
{
if (result is Stream)
return WebContentFormat.Raw;
switch (src) {
case WebMessageFormat.Xml:
return WebContentFormat.Xml;
case WebMessageFormat.Json:
return WebContentFormat.Json;
}
throw new SystemException ("INTERNAL ERROR: should not happen");
}
protected string GetMediaTypeString (WebContentFormat fmt)
{
switch (fmt) {
case WebContentFormat.Raw:
return "application/octet-stream";
case WebContentFormat.Json:
return "application/json";
case WebContentFormat.Xml:
default:
return "application/xml";
}
}
protected void CheckMessageVersion (MessageVersion messageVersion)
{
if (messageVersion == null)
throw new ArgumentNullException ("messageVersion");
if (!MessageVersion.None.Equals (messageVersion))
throw new ArgumentException (String.Format ("Only MessageVersion.None is supported. {0} is not.", messageVersion));
}
protected MessageDescription GetMessageDescription (MessageDirection dir)
{
foreach (MessageDescription md in operation.Messages)
if (md.Direction == dir)
return md;
throw new SystemException ("INTERNAL ERROR: no corresponding message description for the specified direction: " + dir);
}
protected XmlObjectSerializer GetSerializer (WebContentFormat msgfmt, bool isWrapped, MessagePartDescription part)
{
if (part.Type == typeof (void))
return null; // no serialization should be done.
switch (msgfmt) {
case WebContentFormat.Xml:
if (xml_serializer == null)
xml_serializer = isWrapped ? new DataContractSerializer (part.Type, part.Name, part.Namespace) : new DataContractSerializer (part.Type);
return xml_serializer;
case WebContentFormat.Json:
// FIXME: after name argument they are hack
if (json_serializer == null)
json_serializer = isWrapped ? new DataContractJsonSerializer (part.Type, BodyName ?? part.Name, null, 0x100000, false, null, true) : new DataContractJsonSerializer (part.Type);
return json_serializer;
default:
throw new NotImplementedException (msgfmt.ToString ());
}
}
XmlObjectSerializer xml_serializer, json_serializer;
protected object DeserializeObject (XmlObjectSerializer serializer, Message message, MessageDescription md, bool isWrapped, WebContentFormat fmt)
{
// FIXME: handle ref/out parameters
var reader = message.GetReaderAtBodyContents ();
reader.MoveToContent ();
bool wasEmptyElement = reader.IsEmptyElement;
if (isWrapped) {
if (fmt == WebContentFormat.Json)
reader.ReadStartElement ("root", String.Empty); // note that the wrapper name is passed to the serializer.
else
reader.ReadStartElement (md.Body.WrapperName, md.Body.WrapperNamespace);
}
var ret = (serializer == null) ? null : ReadObjectBody (serializer, reader);
if (isWrapped && !wasEmptyElement)
reader.ReadEndElement ();
return ret;
}
protected object ReadObjectBody (XmlObjectSerializer serializer, XmlReader reader)
{
#if MOBILE
return (serializer is DataContractJsonSerializer) ?
((DataContractJsonSerializer) serializer).ReadObject (reader) :
((DataContractSerializer) serializer).ReadObject (reader, true);
#else
return serializer.ReadObject (reader, true);
#endif
}
internal class RequestClientFormatter : WebClientMessageFormatter
{
public RequestClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public override object DeserializeReply (Message message, object [] parameters)
{
throw new NotSupportedException ();
}
}
internal class ReplyClientFormatter : WebClientMessageFormatter
{
public ReplyClientFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public override Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
{
throw new NotSupportedException ();
}
}
#if !MOBILE
internal class RequestDispatchFormatter : WebDispatchMessageFormatter
{
public RequestDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public override Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
{
throw new NotSupportedException ();
}
}
internal class ReplyDispatchFormatter : WebDispatchMessageFormatter
{
public ReplyDispatchFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public override void DeserializeRequest (Message message, object [] parameters)
{
throw new NotSupportedException ();
}
}
#endif
internal abstract class WebClientMessageFormatter : WebMessageFormatter, IClientMessageFormatter
{
IClientMessageFormatter default_formatter;
protected WebClientMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public virtual Message SerializeRequest (MessageVersion messageVersion, object [] parameters)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
CheckMessageVersion (messageVersion);
var c = new Dictionary<string,string> ();
MessageDescription md = GetMessageDescription (MessageDirection.Input);
Message ret;
Uri to;
object msgpart = null;
for (int i = 0; i < parameters.Length; i++) {
var p = md.Body.Parts [i];
string name = p.Name.ToUpper (CultureInfo.InvariantCulture);
if (UriTemplate.PathSegmentVariableNames.Contains (name) ||
UriTemplate.QueryValueVariableNames.Contains (name))
c.Add (name, parameters [i] != null ? Converter.ConvertValueToString (parameters [i], parameters [i].GetType ()) : null);
else {
// FIXME: bind as a message part
if (msgpart == null)
msgpart = parameters [i];
else
throw new NotImplementedException (String.Format ("More than one parameters including {0} that are not contained in the URI template {1} was found.", p.Name, UriTemplate));
}
}
ret = Message.CreateMessage (messageVersion, (string) null, msgpart);
to = UriTemplate.BindByName (Endpoint.Address.Uri, c);
ret.Headers.To = to;
var hp = new HttpRequestMessageProperty ();
hp.Method = Info.Method;
WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat;
var contentFormat = ToContentFormat (msgfmt, msgpart);
string mediaType = GetMediaTypeString (contentFormat);
// FIXME: get encoding from somewhere
hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8";
#if !MOBILE
if (WebOperationContext.Current != null)
WebOperationContext.Current.OutgoingRequest.Apply (hp);
#endif
// FIXME: set hp.SuppressEntityBody for some cases.
ret.Properties.Add (HttpRequestMessageProperty.Name, hp);
var wp = new WebBodyFormatMessageProperty (ToContentFormat (Info.IsRequestFormatSetExplicitly ? Info.RequestFormat : Behavior.DefaultOutgoingRequestFormat, null));
ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
return ret;
}
public virtual object DeserializeReply (Message message, object [] parameters)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
CheckMessageVersion (message.Version);
#if !MOBILE
if (OperationContext.Current != null) {
// Set response in the context
OperationContext.Current.IncomingMessage = message;
}
#endif
if (message.IsEmpty)
return null; // empty message, could be returned by HttpReplyChannel.
string pname = WebBodyFormatMessageProperty.Name;
if (!message.Properties.ContainsKey (pname))
throw new SystemException ("INTERNAL ERROR: it expects WebBodyFormatMessageProperty existence");
var wp = (WebBodyFormatMessageProperty) message.Properties [pname];
var fmt = wp != null ? wp.Format : WebContentFormat.Xml;
var md = GetMessageDescription (MessageDirection.Output);
var serializer = GetSerializer (wp.Format, IsResponseBodyWrapped, md.Body.ReturnValue);
var ret = DeserializeObject (serializer, message, md, IsResponseBodyWrapped, fmt);
return ret;
}
}
internal class WrappedBodyWriter : BodyWriter
{
public WrappedBodyWriter (object value, XmlObjectSerializer serializer, string name, string ns, WebContentFormat fmt)
: base (true)
{
this.name = name;
this.ns = ns;
this.value = value;
this.serializer = serializer;
this.fmt = fmt;
}
WebContentFormat fmt;
string name, ns;
object value;
XmlObjectSerializer serializer;
#if !MOBILE
protected override BodyWriter OnCreateBufferedCopy (int maxBufferSize)
{
return new WrappedBodyWriter (value, serializer, name, ns, fmt);
}
#endif
protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
{
switch (fmt) {
case WebContentFormat.Raw:
WriteRawContents (writer);
break;
case WebContentFormat.Json:
WriteJsonBodyContents (writer);
break;
case WebContentFormat.Xml:
WriteXmlBodyContents (writer);
break;
}
}
void WriteRawContents (XmlDictionaryWriter writer)
{
throw new NotSupportedException ("Some unsupported sequence of writing operation occured. It is likely a missing feature.");
}
void WriteJsonBodyContents (XmlDictionaryWriter writer)
{
if (name != null) {
writer.WriteStartElement ("root");
writer.WriteAttributeString ("type", "object");
}
WriteObject (serializer, writer, value);
if (name != null)
writer.WriteEndElement ();
}
void WriteXmlBodyContents (XmlDictionaryWriter writer)
{
if (name != null)
writer.WriteStartElement (name, ns);
WriteObject (serializer, writer, value);
if (name != null)
writer.WriteEndElement ();
}
void WriteObject (XmlObjectSerializer serializer, XmlDictionaryWriter writer, object value)
{
if (serializer != null){
#if MOBILE
if (serializer is DataContractJsonSerializer)
((DataContractJsonSerializer) serializer).WriteObject (writer, value);
else
((DataContractSerializer) serializer).WriteObject (writer, value);
#else
serializer.WriteObject (writer, value);
#endif
}
}
}
#if !MOBILE
internal abstract class WebDispatchMessageFormatter : WebMessageFormatter, IDispatchMessageFormatter
{
protected WebDispatchMessageFormatter (OperationDescription operation, ServiceEndpoint endpoint, QueryStringConverter converter, WebHttpBehavior behavior)
: base (operation, endpoint, converter, behavior)
{
}
public virtual Message SerializeReply (MessageVersion messageVersion, object [] parameters, object result)
{
try {
return SerializeReplyCore (messageVersion, parameters, result);
} finally {
if (WebOperationContext.Current != null)
OperationContext.Current.Extensions.Remove (WebOperationContext.Current);
}
}
Message SerializeReplyCore (MessageVersion messageVersion, object [] parameters, object result)
{
// parameters could be null.
// result could be null. For Raw output, it becomes no output.
CheckMessageVersion (messageVersion);
MessageDescription md = GetMessageDescription (MessageDirection.Output);
// FIXME: use them.
// var dcob = Operation.Behaviors.Find<DataContractSerializerOperationBehavior> ();
// XmlObjectSerializer xos = dcob.CreateSerializer (result.GetType (), md.Body.WrapperName, md.Body.WrapperNamespace, null);
// var xsob = Operation.Behaviors.Find<XmlSerializerOperationBehavior> ();
// XmlSerializer [] serializers = XmlSerializer.FromMappings (xsob.GetXmlMappings ().ToArray ());
WebMessageFormat msgfmt = Info.IsResponseFormatSetExplicitly ? Info.ResponseFormat : Behavior.DefaultOutgoingResponseFormat;
XmlObjectSerializer serializer = null;
// FIXME: serialize ref/out parameters as well.
string name = null, ns = null;
switch (msgfmt) {
case WebMessageFormat.Xml:
serializer = GetSerializer (WebContentFormat.Xml, IsResponseBodyWrapped, md.Body.ReturnValue);
name = IsResponseBodyWrapped ? md.Body.WrapperName : null;
ns = IsResponseBodyWrapped ? md.Body.WrapperNamespace : null;
break;
case WebMessageFormat.Json:
serializer = GetSerializer (WebContentFormat.Json, IsResponseBodyWrapped, md.Body.ReturnValue);
name = IsResponseBodyWrapped ? (BodyName ?? md.Body.ReturnValue.Name) : null;
ns = String.Empty;
break;
}
var contentFormat = ToContentFormat (msgfmt, result);
string mediaType = GetMediaTypeString (contentFormat);
Message ret = contentFormat == WebContentFormat.Raw ? new RawMessage ((Stream) result) : Message.CreateMessage (MessageVersion.None, null, new WrappedBodyWriter (result, serializer, name, ns, contentFormat));
// Message properties
var hp = new HttpResponseMessageProperty ();
// FIXME: get encoding from somewhere
hp.Headers ["Content-Type"] = mediaType + "; charset=utf-8";
// apply user-customized HTTP results via WebOperationContext.
if (WebOperationContext.Current != null) // this formatter must be available outside ServiceHost.
WebOperationContext.Current.OutgoingResponse.Apply (hp);
// FIXME: fill some properties if required.
ret.Properties.Add (HttpResponseMessageProperty.Name, hp);
var wp = new WebBodyFormatMessageProperty (contentFormat);
ret.Properties.Add (WebBodyFormatMessageProperty.Name, wp);
return ret;
}
public virtual void DeserializeRequest (Message message, object [] parameters)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
CheckMessageVersion (message.Version);
IncomingWebRequestContext iwc = null;
if (OperationContext.Current != null) {
OperationContext.Current.Extensions.Add (new WebOperationContext (OperationContext.Current));
iwc = WebOperationContext.Current.IncomingRequest;
}
var wp = message.Properties [WebBodyFormatMessageProperty.Name] as WebBodyFormatMessageProperty;
var fmt = wp != null ? wp.Format : WebContentFormat.Xml;
Uri to = message.Headers.To;
UriTemplateMatch match = to == null ? null : UriTemplate.Match (Endpoint.Address.Uri, to);
if (match != null && iwc != null)
iwc.UriTemplateMatch = match;
MessageDescription md = GetMessageDescription (MessageDirection.Input);
for (int i = 0; i < parameters.Length; i++) {
var p = md.Body.Parts [i];
string name = p.Name.ToUpperInvariant ();
if (fmt == WebContentFormat.Raw && p.Type == typeof (Stream)) {
var rmsg = (RawMessage) message;
parameters [i] = rmsg.Stream;
} else {
var str = match.BoundVariables [name];
if (str != null)
parameters [i] = Converter.ConvertStringToValue (str, p.Type);
else {
if (info.Method != "GET") {
var serializer = GetSerializer (fmt, IsRequestBodyWrapped, p);
parameters [i] = DeserializeObject (serializer, message, md, IsRequestBodyWrapped, fmt);
}
// for GET Uri template parameters, there is no <anyType xsi:nil='true' />. So just skip the member.
}
}
}
}
}
#endif
internal class RawMessage : Message
{
public RawMessage (Stream stream)
{
this.Stream = stream;
headers = new MessageHeaders (MessageVersion.None);
properties = new MessageProperties ();
}
public override MessageVersion Version {
get { return MessageVersion.None; }
}
MessageHeaders headers;
public override MessageHeaders Headers {
get { return headers; }
}
MessageProperties properties;
public override MessageProperties Properties {
get { return properties; }
}
public Stream Stream { get; private set; }
protected override void OnWriteBodyContents (XmlDictionaryWriter writer)
{
writer.WriteString ("-- message body is raw binary --");
}
protected override MessageBuffer OnCreateBufferedCopy (int maxBufferSize)
{
var ms = Stream as MemoryStream;
if (ms == null) {
ms = new MemoryStream ();
Stream.CopyTo (ms);
this.Stream = ms;
}
return new RawMessageBuffer (ms.ToArray (), headers, properties);
}
}
internal class RawMessageBuffer : MessageBuffer
{
byte [] buffer;
MessageHeaders headers;
MessageProperties properties;
public RawMessageBuffer (byte [] buffer, MessageHeaders headers, MessageProperties properties)
{
this.buffer = buffer;
this.headers = new MessageHeaders (headers);
this.properties = new MessageProperties (properties);
}
public override int BufferSize {
get { return buffer.Length; }
}
public override void Close ()
{
}
public override Message CreateMessage ()
{
var msg = new RawMessage (new MemoryStream (buffer));
msg.Headers.CopyHeadersFrom (headers);
msg.Properties.CopyProperties (properties);
return msg;
}
}
}
}