From 2dc1b63240b7916fda888d1a1e0edcd2813d2ce0 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Wed, 14 Nov 2012 13:16:08 +0200 Subject: [PATCH] Ignoring BOM in HttpEntityManager. --- .../Transport/Http/auto_convertion.cs | 24 +- .../Transport/Http/ping_controller_should.cs | 1 + .../EventStore.Core/EventStore.Core.csproj | 8 +- .../Transport/Http/AutoEventConverter.cs | 18 +- .../Services/Transport/Http/Codecs.cs | 378 ------------------ .../Services/Transport/Http/Codecs/Codec.cs | 49 +++ .../Transport/Http/Codecs/CustomCodec.cs | 54 +++ .../Transport/Http/Codecs/JsonCodec.cs | 72 ++++ .../Transport/Http/Codecs/ManualEncoding.cs | 30 ++ .../Services/Transport/Http/Codecs/NoCodec.cs | 30 ++ .../Transport/Http/Codecs/TextCodec.cs | 33 ++ .../Transport/Http/Codecs/XmlCodec.cs | 83 ++++ .../Http/Controllers/AdminController.cs | 1 + .../Controllers/AtomControllerDefinitions.cs | 4 +- .../Http/Controllers/PingController.cs | 1 + .../Controllers/ReadEventDataController.cs | 1 + .../Http/Controllers/StatController.cs | 1 + .../Http/Controllers/WebSiteController.cs | 1 + .../Services/Transport/Http/HttpService.cs | 1 + .../EventStore.Core/Util/MiniWeb.cs | 1 + .../Services/Http/ProjectionsController.cs | 1 + .../DvuBasic/BankAccountBasicProducer.cs | 1 + .../RunTestScenarios/BankAccountEvent.cs | 1 + .../ProjectionsKillScenario.cs | 1 + .../Commands/WriteFloodHttpProcessor.cs | 1 + .../WriteFloodWaitingHttpProcessor.cs | 1 + .../Commands/WriteHttpProcessor.cs | 1 + .../Commands/WriteLongTermHttpProcessor.cs | 1 + .../EntityManagement/HttpEntity.cs | 2 +- .../EntityManagement/HttpEntityManager.cs | 31 +- 30 files changed, 411 insertions(+), 421 deletions(-) delete mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/Codec.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/CustomCodec.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/JsonCodec.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/ManualEncoding.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/NoCodec.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/TextCodec.cs create mode 100644 src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/XmlCodec.cs diff --git a/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/auto_convertion.cs b/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/auto_convertion.cs index 6d8d9bc62b1..3cd1320b460 100644 --- a/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/auto_convertion.cs +++ b/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/auto_convertion.cs @@ -5,6 +5,7 @@ using EventStore.Core.Messages; using EventStore.Core.Services.Storage.ReaderIndex; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Core.TransactionLog.LogRecords; using NUnit.Framework; using System.Linq; @@ -52,14 +53,14 @@ private static string XmlReadFormat } } - public static byte[] GetJsonWrite(string data, string metadata) + public static string GetJsonWrite(string data, string metadata) { - return Encoding.UTF8.GetBytes(string.Format(JsonWriteFormat, - "-1", - String.Format("\"{0}\"", Guid.NewGuid()), - "\"type\"", - data, - metadata)); + return string.Format(JsonWriteFormat, + "-1", + String.Format("\"{0}\"", Guid.NewGuid()), + "\"type\"", + data, + metadata); } public static string GetJsonReadResult(ClientMessage.ReadEventCompleted completed, bool dataJson = true, bool metadataJson = true) @@ -72,14 +73,9 @@ public static string GetJsonReadResult(ClientMessage.ReadEventCompleted complete metadataJson ? JsonMetadata : WrapIntoQuotes(AsString(completed.Record.Metadata))); } - public static byte[] GetXmlWrite(string data, string metadata, bool withBom = true) + public static string GetXmlWrite(string data, string metadata) { - IEnumerable result = Enumerable.Empty(); - if (withBom) - result = result.Concat(Encoding.UTF8.GetPreamble()); - result = result.Concat(Encoding.UTF8.GetBytes( - string.Format(XmlWriteFormat, "-1", Guid.NewGuid(), "type", data, metadata))); - return result.ToArray(); + return string.Format(XmlWriteFormat, "-1", Guid.NewGuid(), "type", data, metadata); } public static string GetXmlReadResult(ClientMessage.ReadEventCompleted completed, bool dataJson = true, bool metadataJson = true) diff --git a/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/ping_controller_should.cs b/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/ping_controller_should.cs index 424d8e04002..4a9b54200d4 100644 --- a/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/ping_controller_should.cs +++ b/src/EventStore/EventStore.Core.Tests/Services/Transport/Http/ping_controller_should.cs @@ -31,6 +31,7 @@ using EventStore.Common.Utils; using EventStore.Core.Messages; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using NUnit.Framework; diff --git a/src/EventStore/EventStore.Core/EventStore.Core.csproj b/src/EventStore/EventStore.Core/EventStore.Core.csproj index 3acd2dadb7f..f3add1bc6f1 100644 --- a/src/EventStore/EventStore.Core/EventStore.Core.csproj +++ b/src/EventStore/EventStore.Core/EventStore.Core.csproj @@ -144,6 +144,12 @@ + + + + + + @@ -209,7 +215,7 @@ - + diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/AutoEventConverter.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/AutoEventConverter.cs index 14cbb2d7cec..0e4be375db0 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/AutoEventConverter.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/AutoEventConverter.cs @@ -34,6 +34,7 @@ using EventStore.Common.Log; using EventStore.Core.Data; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Core.TransactionLog.LogRecords; using EventStore.Transport.Http; using Newtonsoft.Json; @@ -77,7 +78,7 @@ public static string SmartFormat(ClientMessage.ReadEventCompleted completed, ICo } } - public static Tuple SmartParse(byte[] request, ICodec sourceCodec) + public static Tuple SmartParse(string request, ICodec sourceCodec) { var write = Load(request, sourceCodec); if (write == null || write.Events == null || write.Events.Length == 0) @@ -87,7 +88,7 @@ public static string SmartFormat(ClientMessage.ReadEventCompleted completed, ICo return new Tuple(write.ExpectedVersion, events); } - private static HttpClientMessageDto.WriteEventsDynamic Load(byte[] data, ICodec sourceCodec) + private static HttpClientMessageDto.WriteEventsDynamic Load(string data, ICodec sourceCodec) { switch(sourceCodec.ContentType) { @@ -105,21 +106,16 @@ private static HttpClientMessageDto.WriteEventsDynamic Load(byte[] data, ICodec } } - private static HttpClientMessageDto.WriteEventsDynamic LoadFromJson(byte[] json) + private static HttpClientMessageDto.WriteEventsDynamic LoadFromJson(string json) { - return Codec.Json.From(Encoding.UTF8.GetString(json)); + return Codec.Json.From(json); } - private static HttpClientMessageDto.WriteEventsDynamic LoadFromXml(byte[] xml) + private static HttpClientMessageDto.WriteEventsDynamic LoadFromXml(string xml) { try { - XDocument doc; - using (var memStream = new MemoryStream(xml)) - using (var xmlTextReader = new XmlTextReader(memStream)) - { - doc = XDocument.Load(xmlTextReader); - } + XDocument doc = XDocument.Parse(xml); XNamespace jsonNsValue = "http://james.newtonking.com/projects/json"; XName jsonNsName = XNamespace.Xmlns + "json"; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs.cs deleted file mode 100644 index e39d49ad0e2..00000000000 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs.cs +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright (c) 2012, Event Store LLP -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// Neither the name of the Event Store LLP nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -using System; -using System.IO; -using System.Text; -using System.Xml; -using System.Xml.Serialization; -using EventStore.Common.Log; -using EventStore.Common.Utils; -using EventStore.Transport.Http; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using Formatting = Newtonsoft.Json.Formatting; - -namespace EventStore.Core.Services.Transport.Http -{ - public static class Codec - { - public static readonly NoCodec NoCodec = new NoCodec(); - public static readonly ICodec[] NoCodecs = new ICodec[0]; - public static readonly ManualEncoding ManualEncoding = new ManualEncoding(); - - public static readonly JsonCodec Json = new JsonCodec(); - public static readonly XmlCodec Xml = new XmlCodec(); - public static readonly CustomCodec ApplicationXml = new CustomCodec(Xml, "application/xml"); - public static readonly TextCodec Text = new TextCodec(); - - public static ICodec CreateCustom(ICodec codec, string contentType) - { - return new CustomCodec(codec, contentType); - } - } - - public class NoCodec : ICodec - { - public string ContentType - { - get - { - throw new NotSupportedException(); - } - } - - public bool CanParse(string format) - { - return false; - } - - public bool SuitableForReponse(AcceptComponent component) - { - return false; - } - - public T From(string text) - { - throw new NotSupportedException(); - } - - public string To(T value) - { - throw new NotSupportedException(); - } - } - - public class ManualEncoding : ICodec - { - public string ContentType - { - get - { - throw new InvalidOperationException(); - } - } - - public bool CanParse(string format) - { - return true; - } - - public bool SuitableForReponse(AcceptComponent component) - { - return true; - } - - public T From(string text) - { - throw new InvalidOperationException(); - } - - public string To(T value) - { - throw new InvalidOperationException(); - } - } - - public class CustomCodec : ICodec - { - public ICodec BaseCodec - { - get - { - return _codec; - } - } - - private readonly ICodec _codec; - private readonly string _contentType; - private readonly string _type; - private readonly string _subtype; - - internal CustomCodec(ICodec codec, string contentType) - { - Ensure.NotNull(codec, "codec"); - Ensure.NotNull(contentType, "contentType"); - - _codec = codec; - _contentType = contentType; - var parts = contentType.Split(new[] {'/'}, 2); - if (parts.Length != 2) - throw new ArgumentException("contentType"); - _type = parts[0]; - _subtype = parts[1]; - } - - public string ContentType - { - get - { - return _contentType; - } - } - - public bool CanParse(string format) - { - return string.Equals(format, _contentType, StringComparison.OrdinalIgnoreCase); - } - - public bool SuitableForReponse(AcceptComponent component) - { - return component.MediaType == "*" - || (string.Equals(component.MediaType, _type) - && (component.MediaSubtype == "*" - || string.Equals(component.MediaSubtype, _subtype, StringComparison.OrdinalIgnoreCase))); - } - - public T From(string text) - { - return _codec.From(text); - } - - public string To(T value) - { - return _codec.To(value); - } - } - - public class JsonCodec : ICodec - { - private static readonly ILogger Log = LogManager.GetLoggerFor(); - - private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - DateFormatHandling = DateFormatHandling.IsoDateFormat, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - TypeNameHandling = TypeNameHandling.None, - Converters = new JsonConverter[] - { - new StringEnumConverter() - } - }; - - public static Formatting Formatting = Formatting.Indented; - - public string ContentType - { - get - { - return EventStore.Transport.Http.ContentType.Json; - } - } - - public bool CanParse(string format) - { - return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); - } - - public bool SuitableForReponse(AcceptComponent component) - { - return component.MediaType == "*" - || (string.Equals(component.MediaType, "application") - && (component.MediaSubtype == "*" - || string.Equals(component.MediaSubtype, "json", StringComparison.OrdinalIgnoreCase))); - } - - public T From(string text) - { - try - { - return JsonConvert.DeserializeObject(text, JsonSettings); - } - catch (Exception e) - { - Log.ErrorException(e, "'{0}' is not a valid serialized {1}", text, typeof(T).FullName); - return default(T); - } - } - - public string To(T value) - { - try - { - return JsonConvert.SerializeObject(value, Formatting, JsonSettings); - } - catch (Exception ex) - { - Log.ErrorException(ex, "Error serializing object {0}", value); - return null; - } - } - } - - public class XmlCodec : ICodec - { - private static readonly ILogger Log = LogManager.GetLoggerFor(); - - public string ContentType - { - get - { - return EventStore.Transport.Http.ContentType.Xml; - } - } - - public bool CanParse(string format) - { - return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); - } - - public bool SuitableForReponse(AcceptComponent component) - { - return component.MediaType == "*" - || (string.Equals(component.MediaType, "text") - && (component.MediaSubtype == "*" - || string.Equals(component.MediaSubtype, "xml", StringComparison.OrdinalIgnoreCase))); - } - - public T From(string text) - { - if (string.IsNullOrEmpty(text)) - return default(T); - - try - { - using (var reader = new StringReader(text)) - return (T) new XmlSerializer(typeof (T)).Deserialize(reader); - } - catch (Exception e) - { - Log.ErrorException(e, "'{0}' is not a valid serialized {1}", text, typeof(T).FullName); - return default(T); - } - } - - public string To(T value) - { - if ((object)value == null) - return null; - - var serializable = value as IXmlSerializable; - if (serializable != null) - return ToSerializable(serializable); - - try - { - using (var memory = new MemoryStream()) -// using (var writer = new XmlTextWriter(memory, new UTF8Encoding(false))) - { - //new XmlSerializer(typeof (T)).Serialize(writer, value); - new XmlSerializer(typeof (T)).Serialize(memory, value); - memory.Flush(); - memory.Seek(0L, SeekOrigin.Begin); - return Encoding.UTF8.GetString(memory.GetBuffer(), 0, (int)memory.Length); - } - } - catch (Exception ex) - { - Log.ErrorException(ex, "Error serializing object {0}", value); - return null; - } - } - - private string ToSerializable(IXmlSerializable serializable) - { - try - { - using (var memory = new MemoryStream()) - using (var writer = XmlWriter.Create(memory)) - { - writer.WriteStartDocument(); - serializable.WriteXml(writer); - writer.WriteEndDocument(); - writer.Flush(); - - memory.Seek(0L, SeekOrigin.Begin); - return Encoding.UTF8.GetString(memory.GetBuffer(), 0, (int)memory.Length); - } - } - catch (Exception e) - { - Log.ErrorException(e, "Error serializing object of type {0}", serializable.GetType().FullName); - return null; - } - } - } - - public class TextCodec : ICodec - { - public string ContentType - { - get - { - return EventStore.Transport.Http.ContentType.PlainText; - } - } - - public bool CanParse(string format) - { - return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); - } - - public bool SuitableForReponse(AcceptComponent component) - { - return component.MediaType == "*" - || (string.Equals(component.MediaType, "text") - && (component.MediaSubtype == "*" - || string.Equals(component.MediaSubtype, "plain", StringComparison.OrdinalIgnoreCase))); - } - - public T From(string text) - { - throw new NotSupportedException(); - } - - public string To(T value) - { - return ((object) value) != null ? value.ToString() : null; - } - } -} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/Codec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/Codec.cs new file mode 100644 index 00000000000..49a70ad1657 --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/Codec.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2012, Event Store LLP +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// Neither the name of the Event Store LLP nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public static class Codec + { + public static readonly NoCodec NoCodec = new NoCodec(); + public static readonly ICodec[] NoCodecs = new ICodec[0]; + public static readonly ManualEncoding ManualEncoding = new ManualEncoding(); + + public static readonly JsonCodec Json = new JsonCodec(); + public static readonly XmlCodec Xml = new XmlCodec(); + public static readonly CustomCodec ApplicationXml = new CustomCodec(Xml, ContentType.ApplicationXml); + public static readonly TextCodec Text = new TextCodec(); + + public static ICodec CreateCustom(ICodec codec, string contentType) + { + return new CustomCodec(codec, contentType); + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/CustomCodec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/CustomCodec.cs new file mode 100644 index 00000000000..21e7b20f004 --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/CustomCodec.cs @@ -0,0 +1,54 @@ +using System; +using EventStore.Common.Utils; +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class CustomCodec : ICodec + { + public ICodec BaseCodec { get { return _codec; } } + public string ContentType { get { return _contentType; } } + + private readonly ICodec _codec; + private readonly string _contentType; + private readonly string _type; + private readonly string _subtype; + + internal CustomCodec(ICodec codec, string contentType) + { + Ensure.NotNull(codec, "codec"); + Ensure.NotNull(contentType, "contentType"); + + _codec = codec; + _contentType = contentType; + var parts = contentType.Split(new[] {'/'}, 2); + if (parts.Length != 2) + throw new ArgumentException("contentType"); + _type = parts[0]; + _subtype = parts[1]; + } + + public bool CanParse(string format) + { + return string.Equals(format, _contentType, StringComparison.OrdinalIgnoreCase); + } + + public bool SuitableForReponse(AcceptComponent component) + { + return component.MediaType == "*" + || (string.Equals(component.MediaType, _type, StringComparison.OrdinalIgnoreCase) + && (component.MediaSubtype == "*" + || string.Equals(component.MediaSubtype, _subtype, StringComparison.OrdinalIgnoreCase))); + } + + public T From(string text) + { + return _codec.From(text); + } + + public string To(T value) + { + return _codec.To(value); + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/JsonCodec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/JsonCodec.cs new file mode 100644 index 00000000000..465e2e0acdf --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/JsonCodec.cs @@ -0,0 +1,72 @@ +using System; +using EventStore.Common.Log; +using EventStore.Transport.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class JsonCodec : ICodec + { + public static Formatting Formatting = Formatting.Indented; + + private static readonly ILogger Log = LogManager.GetLoggerFor(); + + private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + DateFormatHandling = DateFormatHandling.IsoDateFormat, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + TypeNameHandling = TypeNameHandling.None, + Converters = new JsonConverter[] + { + new StringEnumConverter() + } + }; + + + public string ContentType { get { return EventStore.Transport.Http.ContentType.Json; } } + + public bool CanParse(string format) + { + return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); + } + + public bool SuitableForReponse(AcceptComponent component) + { + return component.MediaType == "*" + || (string.Equals(component.MediaType, "application", StringComparison.OrdinalIgnoreCase) + && (component.MediaSubtype == "*" + || string.Equals(component.MediaSubtype, "json", StringComparison.OrdinalIgnoreCase))); + } + + public T From(string text) + { + try + { + return JsonConvert.DeserializeObject(text, JsonSettings); + } + catch (Exception e) + { + Log.ErrorException(e, "'{0}' is not a valid serialized {1}", text, typeof(T).FullName); + return default(T); + } + } + + public string To(T value) + { + try + { + return JsonConvert.SerializeObject(value, Formatting, JsonSettings); + } + catch (Exception ex) + { + Log.ErrorException(ex, "Error serializing object {0}", value); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/ManualEncoding.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/ManualEncoding.cs new file mode 100644 index 00000000000..beaf59cb3eb --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/ManualEncoding.cs @@ -0,0 +1,30 @@ +using System; +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class ManualEncoding : ICodec + { + public string ContentType { get { throw new InvalidOperationException(); } } + + public bool CanParse(string format) + { + return true; + } + + public bool SuitableForReponse(AcceptComponent component) + { + return true; + } + + public T From(string text) + { + throw new InvalidOperationException(); + } + + public string To(T value) + { + throw new InvalidOperationException(); + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/NoCodec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/NoCodec.cs new file mode 100644 index 00000000000..e8dcb9b4b5b --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/NoCodec.cs @@ -0,0 +1,30 @@ +using System; +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class NoCodec : ICodec + { + public string ContentType { get { throw new NotSupportedException(); } } + + public bool CanParse(string format) + { + return false; + } + + public bool SuitableForReponse(AcceptComponent component) + { + return false; + } + + public T From(string text) + { + throw new NotSupportedException(); + } + + public string To(T value) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/TextCodec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/TextCodec.cs new file mode 100644 index 00000000000..405cf6356ee --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/TextCodec.cs @@ -0,0 +1,33 @@ +using System; +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class TextCodec : ICodec + { + public string ContentType { get { return EventStore.Transport.Http.ContentType.PlainText; } } + + public bool CanParse(string format) + { + return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); + } + + public bool SuitableForReponse(AcceptComponent component) + { + return component.MediaType == "*" + || (string.Equals(component.MediaType, "text", StringComparison.OrdinalIgnoreCase) + && (component.MediaSubtype == "*" + || string.Equals(component.MediaSubtype, "plain", StringComparison.OrdinalIgnoreCase))); + } + + public T From(string text) + { + throw new NotSupportedException(); + } + + public string To(T value) + { + return ((object) value) != null ? value.ToString() : null; + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/XmlCodec.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/XmlCodec.cs new file mode 100644 index 00000000000..1cd598b234f --- /dev/null +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Codecs/XmlCodec.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using EventStore.Common.Log; +using EventStore.Transport.Http; + +namespace EventStore.Core.Services.Transport.Http.Codecs +{ + public class XmlCodec : ICodec + { + private static readonly ILogger Log = LogManager.GetLoggerFor(); + private static readonly UTF8Encoding UTF8 = new UTF8Encoding(false); // we use our own encoding which doesn't produce BOM + + public string ContentType { get { return EventStore.Transport.Http.ContentType.Xml; } } + + public bool CanParse(string format) + { + return string.Equals(ContentType, format, StringComparison.OrdinalIgnoreCase); + } + + public bool SuitableForReponse(AcceptComponent component) + { + return component.MediaType == "*" + || (string.Equals(component.MediaType, "text", StringComparison.OrdinalIgnoreCase) + && (component.MediaSubtype == "*" + || string.Equals(component.MediaSubtype, "xml", StringComparison.OrdinalIgnoreCase))); + } + + public T From(string text) + { + if (string.IsNullOrEmpty(text)) + return default(T); + + try + { + using (var reader = new StringReader(text)) + { + return (T) new XmlSerializer(typeof (T)).Deserialize(reader); + } + } + catch (Exception e) + { + Log.ErrorException(e, "'{0}' is not a valid serialized {1}", text, typeof(T).FullName); + return default(T); + } + } + + public string To(T value) + { + if ((object)value == null) + return null; + + try + { + using (var memory = new MemoryStream()) + using (var writer = new XmlTextWriter(memory, UTF8)) + { + var serializable = value as IXmlSerializable; + if (serializable != null) + { + writer.WriteStartDocument(); + serializable.WriteXml(writer); + writer.WriteEndDocument(); + } + else + { + new XmlSerializer(typeof (T)).Serialize(writer, value); + } + + writer.Flush(); + return UTF8.GetString(memory.GetBuffer(), 0, (int)memory.Length); + } + } + catch (Exception exc) + { + Log.ErrorException(exc, "Error serializing object of type {0}", value.GetType().FullName); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AdminController.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AdminController.cs index d5132115bad..78e06f46fc6 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AdminController.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AdminController.cs @@ -29,6 +29,7 @@ using EventStore.Common.Log; using EventStore.Core.Bus; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AtomControllerDefinitions.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AtomControllerDefinitions.cs index 047e21b07f1..db0b47cec98 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AtomControllerDefinitions.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/AtomControllerDefinitions.cs @@ -26,12 +26,12 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // using System; -using System.Linq; using System.Text; using EventStore.Common.Log; using EventStore.Core.Bus; using EventStore.Core.Data; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.Atom; using EventStore.Transport.Http.EntityManagement; @@ -387,7 +387,7 @@ public void PostEntry(HttpEntity entity, string stream) e => Log.ErrorException(e, "Error while reading request (POST entry)")); } - private void OnPostEntryRequestRead(HttpEntityManager manager, byte[] body) + private void OnPostEntryRequestRead(HttpEntityManager manager, string body) { var entity = manager.HttpEntity; var stream = (string)manager.AsyncState; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/PingController.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/PingController.cs index c964e66d14b..81edca3a34a 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/PingController.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/PingController.cs @@ -29,6 +29,7 @@ using EventStore.Common.Log; using EventStore.Common.Utils; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/ReadEventDataController.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/ReadEventDataController.cs index d029037a578..e3d7e0b3afe 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/ReadEventDataController.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/ReadEventDataController.cs @@ -28,6 +28,7 @@ using System; using EventStore.Core.Bus; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/StatController.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/StatController.cs index b16fdd9f5e3..c50e0cb6400 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/StatController.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/StatController.cs @@ -30,6 +30,7 @@ using EventStore.Common.Utils; using EventStore.Core.Bus; using EventStore.Core.Messages; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/WebSiteController.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/WebSiteController.cs index 986bd2dc37d..47a04f4c0a0 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/WebSiteController.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/Controllers/WebSiteController.cs @@ -31,6 +31,7 @@ using System.Linq; using System.Text; using EventStore.Core.Bus; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Core.Util; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Services/Transport/Http/HttpService.cs b/src/EventStore/EventStore.Core/Services/Transport/Http/HttpService.cs index 97103c621f2..75d9947bdb8 100644 --- a/src/EventStore/EventStore.Core/Services/Transport/Http/HttpService.cs +++ b/src/EventStore/EventStore.Core/Services/Transport/Http/HttpService.cs @@ -35,6 +35,7 @@ using EventStore.Core.Messages; using EventStore.Core.Messaging; using EventStore.Core.Services.TimerService; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using System.Linq; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Core/Util/MiniWeb.cs b/src/EventStore/EventStore.Core/Util/MiniWeb.cs index 9b389a016d8..9ff606559de 100644 --- a/src/EventStore/EventStore.Core/Util/MiniWeb.cs +++ b/src/EventStore/EventStore.Core/Util/MiniWeb.cs @@ -32,6 +32,7 @@ using System.IO; using EventStore.Common.Log; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.EntityManagement; diff --git a/src/EventStore/EventStore.Projections.Core/Services/Http/ProjectionsController.cs b/src/EventStore/EventStore.Projections.Core/Services/Http/ProjectionsController.cs index 799a9db0cef..0db7bba3f1d 100644 --- a/src/EventStore/EventStore.Projections.Core/Services/Http/ProjectionsController.cs +++ b/src/EventStore/EventStore.Projections.Core/Services/Http/ProjectionsController.cs @@ -33,6 +33,7 @@ using EventStore.Core.Bus; using EventStore.Core.Messaging; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Core.Services.Transport.Http.Controllers; using EventStore.Core.Util; using EventStore.Projections.Core.Messages; diff --git a/src/EventStore/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs b/src/EventStore/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs index 89ba9dd1249..3d841efc312 100644 --- a/src/EventStore/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs +++ b/src/EventStore/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs @@ -30,6 +30,7 @@ using EventStore.Common.Log; using EventStore.Core.Data; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; namespace EventStore.TestClient.Commands.DvuBasic { diff --git a/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs b/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs index f3495490f8c..9487c7751b7 100644 --- a/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs +++ b/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs @@ -31,6 +31,7 @@ using System.Text; using EventStore.ClientAPI; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; namespace EventStore.TestClient.Commands.RunTestScenarios { diff --git a/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs b/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs index ce3a82a0a23..d89aedf3cac 100644 --- a/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs +++ b/src/EventStore/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs @@ -35,6 +35,7 @@ using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.TestClient.Commands.DvuBasic; namespace EventStore.TestClient.Commands.RunTestScenarios diff --git a/src/EventStore/EventStore.TestClient/Commands/WriteFloodHttpProcessor.cs b/src/EventStore/EventStore.TestClient/Commands/WriteFloodHttpProcessor.cs index ee3e16c4fab..71c3ceadef0 100644 --- a/src/EventStore/EventStore.TestClient/Commands/WriteFloodHttpProcessor.cs +++ b/src/EventStore/EventStore.TestClient/Commands/WriteFloodHttpProcessor.cs @@ -34,6 +34,7 @@ using EventStore.Core.Data; using EventStore.Core.Messages; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.Client; using HttpStatusCode = EventStore.Transport.Http.HttpStatusCode; diff --git a/src/EventStore/EventStore.TestClient/Commands/WriteFloodWaitingHttpProcessor.cs b/src/EventStore/EventStore.TestClient/Commands/WriteFloodWaitingHttpProcessor.cs index 337b8b35eb6..f5ba4787133 100644 --- a/src/EventStore/EventStore.TestClient/Commands/WriteFloodWaitingHttpProcessor.cs +++ b/src/EventStore/EventStore.TestClient/Commands/WriteFloodWaitingHttpProcessor.cs @@ -34,6 +34,7 @@ using EventStore.Core.Data; using EventStore.Core.Messages; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.Client; using HttpStatusCode = EventStore.Transport.Http.HttpStatusCode; diff --git a/src/EventStore/EventStore.TestClient/Commands/WriteHttpProcessor.cs b/src/EventStore/EventStore.TestClient/Commands/WriteHttpProcessor.cs index 7fa3f77a143..20774a0d0f0 100644 --- a/src/EventStore/EventStore.TestClient/Commands/WriteHttpProcessor.cs +++ b/src/EventStore/EventStore.TestClient/Commands/WriteHttpProcessor.cs @@ -31,6 +31,7 @@ using EventStore.Core.Data; using EventStore.Core.Messages; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.Client; diff --git a/src/EventStore/EventStore.TestClient/Commands/WriteLongTermHttpProcessor.cs b/src/EventStore/EventStore.TestClient/Commands/WriteLongTermHttpProcessor.cs index d22da089af9..421ec56d625 100644 --- a/src/EventStore/EventStore.TestClient/Commands/WriteLongTermHttpProcessor.cs +++ b/src/EventStore/EventStore.TestClient/Commands/WriteLongTermHttpProcessor.cs @@ -34,6 +34,7 @@ using EventStore.Core.Data; using EventStore.Core.Messages; using EventStore.Core.Services.Transport.Http; +using EventStore.Core.Services.Transport.Http.Codecs; using EventStore.Transport.Http; using EventStore.Transport.Http.Client; using HttpStatusCode = EventStore.Transport.Http.HttpStatusCode; diff --git a/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntity.cs b/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntity.cs index 2efde2d5c59..a09f27e85ed 100644 --- a/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntity.cs +++ b/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntity.cs @@ -89,7 +89,7 @@ public Uri RequestBaseUri Request = request; Response = response; - Manager = HttpEntityManager.Create(this, allowedMethods, onRequestSatisfied); + Manager = new HttpEntityManager(this, allowedMethods, onRequestSatisfied); } } } \ No newline at end of file diff --git a/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntityManager.cs b/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntityManager.cs index 3d31446f6da..8578b54ff96 100644 --- a/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntityManager.cs +++ b/src/EventStore/EventStore.Transport.Http/EntityManagement/HttpEntityManager.cs @@ -27,7 +27,9 @@ // using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Threading; @@ -40,24 +42,14 @@ public sealed class HttpEntityManager { private static readonly ILogger Log = LogManager.GetLoggerFor(); - internal static HttpEntityManager Create(HttpEntity httpEntity, - string[] allowedMethods, - Action onRequestSatisfied) - { - return new HttpEntityManager(httpEntity, allowedMethods, onRequestSatisfied); - } - public object AsyncState { get; set; } - public readonly HttpEntity HttpEntity; private int _processing; private readonly string[] _allowedMethods; private readonly Action _onRequestSatisfied; - private HttpEntityManager(HttpEntity httpEntity, - string[] allowedMethods, - Action onRequestSatisfied) + internal HttpEntityManager(HttpEntity httpEntity, string[] allowedMethods, Action onRequestSatisfied) { Ensure.NotNull(httpEntity, "httpEntity"); Ensure.NotNull(allowedMethods, "allowedMethods"); @@ -157,7 +149,9 @@ private void SetAdditionalHeaders(IEnumerable> head try { foreach (var kvp in headers) + { HttpEntity.Response.AddHeader(kvp.Key, kvp.Value); + } } catch (Exception e) { @@ -197,7 +191,7 @@ public void Reply(int code, string description, Action onError) SetResponseDescription(description); SetResponseType(type); SetRequiredHeaders(); - SetAdditionalHeaders(headers ?? new KeyValuePair[0]); + SetAdditionalHeaders(headers ?? Enumerable.Empty>()); if (response == null || response.Length == 0) { @@ -242,9 +236,18 @@ private void ResponseWritten(object sender, EventArgs eventArgs) public void ReadRequestAsync(Action onSuccess, Action onError) { - ReadRequestAsync((manager, bytes) => onSuccess(manager, Encoding.UTF8.GetString(bytes)), onError); + ReadRequestAsync((manager, bytes) => + { + int offset = 0; + + // check for UTF-8 BOM (0xEF, 0xBB, 0xBF) and skip it safely, if any + if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) + offset = 3; + + onSuccess(manager, Encoding.UTF8.GetString(bytes, offset, bytes.Length - offset)); + }, onError); } - + public void ReadRequestAsync(Action onSuccess, Action onError) { Ensure.NotNull(onSuccess, "onSuccess");