HTTPS Error in Web Api

Topics: Web Api
Aug 12, 2011 at 5:18 PM

Hello All,

I'm having trouble getting WCF (Web API preview 3) working with HTTPS.  

Is there a known solution to this problem?

The provided URI scheme 'https' is invalid; expected 'http'.
Parameter name: context.ListenUriBaseAddress

It works without any issues in HTTP, but soon as we try to access it in HTTPS we receive this error. 

Thanks!

Aug 12, 2011 at 5:45 PM

This is a known issue. See: http://wcf.codeplex.com/discussions/260860

I would definitely recommend moving to Preview 4 as there are breaking changes from 3 to 4 and the latter is quite an improvement.  I'm speculating the WebApi team is working on pushing a preview 5 soon, from what i can gather in the discussions here.

Aug 12, 2011 at 6:30 PM

dgdev,

I just want to verify that this can only be changed by moving to Preview 4, correct?

I can't find HttpConfigurableServiceHostFactory in preview 3.

Thanks,

Luke

Aug 12, 2011 at 8:52 PM

Sorry for the late response ... busy Friday.  I can't say 100% whether HTTPs is completely broken in Preview 3.  Give me 20 minutes to test our WebApi-driven application right now which "should" work with HTTPS.  We've only been testing and developing without SSL.

Aug 12, 2011 at 9:22 PM
Edited Aug 12, 2011 at 9:28 PM

I've setup our development IIS with a self-signed certificate and all web services are working via SSL.  Good to know.

Still, I can't say whether or not Preview 3 is completely broken from HTTPS.  Hopefully someone else can comment on that.

 

"HttpConfigurableServiceHostFactory" lives in namespace "Microsoft.ApplicationServer.Http.Activation".

The temporary codefix within the constructor below is due to a bug in Preview 4.

    /// <summary>
    /// Defines a custom WebApi service host factory for https scheme.
    /// </summary>
    /// <remarks></remarks>
    public class HttpsConfigurableServiceHostFactory : HttpConfigurableServiceHostFactory
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="HttpsConfigurableServiceHostFactory"/> class.
        /// </summary>
        /// <remarks></remarks>
        public HttpsConfigurableServiceHostFactory()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HttpsConfigurableServiceHostFactory"/> class.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <remarks></remarks>
        public HttpsConfigurableServiceHostFactory(IHttpHostConfigurationBuilder builder)
            : base(builder)
        {
            // TODO: (DG) Remove this code with Preview 5
            var of = new HttpOperationHandlerFactory();
            of.Formatters.Clear();
            of.Formatters.Add(new JsonNetMediaTypeFormatter());
            builder.SetOperationHandlerFactory(of);
        }

        /// <summary>
        /// Creates the service host.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="baseAddresses">The base addresses.</param>
        /// <returns>The newly created <see cref="ServiceHost"/>.</returns>
        /// <remarks></remarks>
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            var host = base.CreateServiceHost(serviceType, baseAddresses);
            foreach (var httpBinding in from serviceEndpoint in host.Description.Endpoints
                                        select (HttpBinding)serviceEndpoint.Binding)
            {
                httpBinding.MaxBufferSize = Int32.MaxValue;
                httpBinding.MaxReceivedMessageSize = Int32.MaxValue;
            }

            foreach (var httpBinding in from serviceEndpoint in host.Description.Endpoints
                                        where String.Compare(serviceEndpoint.ListenUri.Scheme, "https", true) == 0
                                        select (HttpBinding)serviceEndpoint.Binding)
            {
                httpBinding.Security.Mode = HttpBindingSecurityMode.Transport;
            }

            return host;
        }
    }

 

Other breaking changes include formatters.  I can't remember the old name, but they're now called MediaTypeFormatter.  Below is a custom Json.NET formatter I've written and tested.

    /// <summary>
    /// Defines a Json <see cref="MediaTypeFormatter"/> using Json.NET serialization/deserialization.
    /// </summary>
    public class JsonNetMediaTypeFormatter : MediaTypeFormatter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="JsonNetMediaTypeFormatter"/> class.
        /// </summary>
        public JsonNetMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/bson"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
        }
        
        /// <summary>
        /// Called when [read from stream].
        /// </summary>
        /// <param name="type">The type of object to deserialize.</param>
        /// <param name="stream">The stream.</param>
        /// <param name="httpContentHeaders">The HTTP content headers.</param>
        /// <returns>The de-serialized object.</returns>
        /// <remarks></remarks>
        public override Object OnReadFromStream(Type type, Stream stream, HttpContentHeaders httpContentHeaders)
        {
            return httpContentHeaders.ContentType.MediaType.ToLower() == "application/bson" 
                ? stream.ReadAsBsonSerializable(type) 
                : stream.ReadAsJsonSerializable(type);
        }

        /// <summary>
        /// Called to write an object to the <paramref name="stream"/>.
        /// </summary>
        /// <param name="type">The type of object to write.</param>
        /// <param name="value">The object instance to write.</param>
        /// <param name="stream">The <see cref="T:System.IO.Stream"/> to which to write.</param>
        /// <param name="httpContentHeaders">The HTTP content headers.</param>
        /// <param name="context">The <see cref="T:System.Net.TransportContext"/>.</param>
        /// <remarks></remarks>
        public override void OnWriteToStream(Type type, Object value, Stream stream, HttpContentHeaders httpContentHeaders, TransportContext context)
        {
            if (httpContentHeaders.ContentType.MediaType.ToLower() == "application/bson")
            {
                stream.WriteAsBsonSerializable(value);
            }
            else
            {
                stream.WriteAsJsonSerializable(value);
            }
        }
    }

    /// <summary>
    /// Defines extension methods to support Json.NET serialization and deserialization.
    /// </summary>
    public static class JsonNetExtensions
    {
        /// <summary>
        /// Deserializes the JSON-serialized stream and casts it to the object type specified.
        /// </summary>
        /// <typeparam name="T">The type of object to deserialize.</typeparam>
        /// <param name="stream">The stream.</param>
        /// <returns>The deserialized object as type <typeparamref name="T"/></returns>
        public static T ReadAsJsonSerializable<T>(this Stream stream) where T : class
        {
            return ReadAsJsonSerializable(stream, typeof(T)) as T;
        }

        /// <summary>
        /// Deserializes the BSON-serialized stream and casts it to the object type specified.
        /// </summary>
        /// <typeparam name="T">The type of object to deserialize.</typeparam>
        /// <param name="stream">The stream.</param>
        /// <returns>The deserialized object as type <typeparamref name="T"/></returns>
        public static T ReadAsBsonSerializable<T>(this Stream stream) where T : class
        {
            return ReadAsBsonSerializable(stream, typeof(T)) as T;
        }

        /// <summary>
        /// Reads the JSON-serialized stream and deserializes it into a CLR object.
        /// </summary>
        /// <param name="stream">The stream to deserialize.</param>
        /// <param name="instanceType">Type of the instance.</param>
        /// <returns>The deserialized object.</returns>
        /// <remarks></remarks>
        public static Object ReadAsJsonSerializable(this Stream stream, Type instanceType)
        {
            if (stream == null)
            {
                return null;
            }

            using (var jsonTextReader = new JsonTextReader(new StreamReader(stream)))
            {
                return Deserialize(jsonTextReader, instanceType);
            }
        }

        /// <summary>
        /// Reads the BSON-serialized stream and deserializes it into a CLR object.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="instanceType">Type of the instance.</param>
        /// <returns>The deserialized object.</returns>
        public static Object ReadAsBsonSerializable(this Stream stream, Type instanceType)
        {
            if (stream == null)
            {
                return null;
            }

            using (var bsonReader = new BsonReader(stream))
            {
                bsonReader.DateTimeKindHandling = DateTimeKind.Utc;

                return Deserialize(bsonReader, instanceType);
            }
        }

        /// <summary>
        /// Serializes the object into JSON and writes the data into the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="instance">The object instance to serialize.</param>
        public static void WriteAsJsonSerializable(this Stream stream, Object instance)
        {
            if (instance == null)
            {
                return;
            }

            using (var jsonTextWriter = new JsonTextWriter(new StreamWriter(stream)) { CloseOutput = false })
            {
                Serialize(jsonTextWriter, instance);
            }
        }

        /// <summary>
        /// Serializes the object instance into BSON and writes the data into the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="instance">The object instance to serialize.</param>
        public static void WriteAsBsonSerializable(this Stream stream, Object instance)
        {
            if (instance == null)
            {
                return;
            }

            using (var bsonWriter = new BsonWriter(stream) { CloseOutput = false, DateTimeKindHandling = DateTimeKind.Utc })
            {
                Serialize(bsonWriter, instance);
            }
        }

        /// <summary>
        /// Gets the json serializer which supports references and circular referencing.
        /// </summary>
        /// <returns>The <see cref="JsonSerializer"/>.</returns>
        /// <remarks></remarks>
        private static JsonSerializer GetJsonSerializer()
        {
            return new JsonSerializer
                {
                    MissingMemberHandling = MissingMemberHandling.Ignore,
                    NullValueHandling = NullValueHandling.Ignore,
                    ObjectCreationHandling = ObjectCreationHandling.Replace,
                    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                };
        }

        private static JsonSerializer GetBsonSerializer()
        {
            return new JsonSerializer
                {
                    MissingMemberHandling = MissingMemberHandling.Ignore,
                    ObjectCreationHandling = ObjectCreationHandling.Replace,
                    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                    TypeNameHandling = TypeNameHandling.Objects
                };
        }

        /// <summary>
        /// Deserializes the specified <see cref="JsonReader"/> stream.
        /// </summary>
        /// <param name="jsonReader">The json reader.</param>
        /// <param name="instanceType">Type of the instance to deserialize.</param>
        /// <returns>The deserialized object.</returns>
        private static Object Deserialize(JsonReader jsonReader, Type instanceType)
        {
            try
            {
                using (jsonReader)
                {
                    var jsonSerializer = jsonReader is BsonReader 
                        ? GetBsonSerializer()
                        : GetJsonSerializer();
                    
                    return jsonSerializer.Deserialize(jsonReader, instanceType);
                }
            }
            catch (JsonReaderException)
            {
                // TODO: (DG) Internal logging...
                jsonReader.Close();
                throw;
            }
            catch (JsonSerializationException)
            {
                // TODO: (DG) Internal logging...
                jsonReader.Close();
                throw;
            }
        }

        /// <summary>
        /// Serializes the specified <see cref="JsonWriter"/> stream.
        /// </summary>
        /// <param name="jsonWriter">The json writer.</param>
        /// <param name="instance">The instance to serialize.</param>
        private static void Serialize(JsonWriter jsonWriter, Object instance)
        {
            try
            {
                var jsonSerializer = jsonWriter is BsonWriter
                        ? GetBsonSerializer()
                        : GetJsonSerializer();

                jsonSerializer.Serialize(jsonWriter, instance);
                jsonWriter.Flush();
            }
            catch (JsonWriterException)
            {
                // TODO: (DG) Internal logging...
                jsonWriter.Close();
                throw;
            }
            catch (JsonSerializationException)
            {
                // TODO: (DG) Internal logging...
                jsonWriter.Close();
                throw;
            }
        }
    }
Aug 12, 2011 at 11:13 PM
Edited Aug 12, 2011 at 11:13 PM

After upgrading to Preview 4, I'm still receiving the same error. 

Here is a snippet of my implementation:   

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        var config = (HttpHostConfiguration)HttpHostConfiguration.Create();
        config.OperationHandlerFactory.Formatters.Add(new JsonPFormatter());
        RouteTable.Routes.MapServiceRoute<RootResource>("", config, new HttpsServiceHostFactory());
    {
{

From Web.config:
<bindings>
      <httpMessageBinding>
        <binding name="configuredBinding" hostNameComparisonMode="Exact" maxBufferPoolSize="1048756" maxReceivedMessageSize="1048756" maxBufferSize="1048756" transferMode="StreamedResponse">
          <security mode="Transport">            
          </security>
        </binding>
      </httpMessageBinding>
    </bindings> 

I debugged with the source from the Web Api and found it's being thrown from Microsoft.ApplicationServer.Http.Channels.HttpMessageEncodingBindingElement.BuildChannelListener @ Line 117:
IChannelListener<IReplyChannel> innerListener = context.BuildInnerChannelListener<IReplyChannel>();

It appears to be thrown before the debugger ever reaches the MapServiceRoute, should I be implementing this farther up the stack?

Thanks