From 406e6cb8132c1b8ade2872d44d7183267dd51ca8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Nov 2016 14:55:12 -0500 Subject: update portable projects --- ServiceStack/FilterAttributeCache.cs | 27 ++ ServiceStack/Host/ActionContext.cs | 27 ++ ServiceStack/Host/ContentTypes.cs | 77 +++++ ServiceStack/Host/HttpResponseStreamWrapper.cs | 95 ++++++ ServiceStack/Host/RestHandler.cs | 200 +++++++++++ ServiceStack/Host/RestPath.cs | 443 +++++++++++++++++++++++++ ServiceStack/Host/ServiceController.cs | 220 ++++++++++++ ServiceStack/Host/ServiceExec.cs | 156 +++++++++ ServiceStack/Host/ServiceMetadata.cs | 27 ++ ServiceStack/HttpHandlerFactory.cs | 27 ++ ServiceStack/HttpRequestExtensions.cs | 127 +++++++ ServiceStack/HttpResponseExtensionsInternal.cs | 237 +++++++++++++ ServiceStack/HttpResult.cs | 250 ++++++++++++++ ServiceStack/HttpUtils.cs | 34 ++ ServiceStack/Properties/AssemblyInfo.cs | 25 ++ ServiceStack/ReflectionExtensions.cs | 270 +++++++++++++++ ServiceStack/ServiceStack.csproj | 131 ++++++++ ServiceStack/ServiceStack.nuget.targets | 6 + ServiceStack/ServiceStack.xproj | 19 ++ ServiceStack/ServiceStackHost.Runtime.cs | 74 +++++ ServiceStack/ServiceStackHost.cs | 104 ++++++ ServiceStack/StringMapTypeDeserializer.cs | 126 +++++++ ServiceStack/UrlExtensions.cs | 33 ++ ServiceStack/packages.config | 3 + ServiceStack/project.json | 17 + 25 files changed, 2755 insertions(+) create mode 100644 ServiceStack/FilterAttributeCache.cs create mode 100644 ServiceStack/Host/ActionContext.cs create mode 100644 ServiceStack/Host/ContentTypes.cs create mode 100644 ServiceStack/Host/HttpResponseStreamWrapper.cs create mode 100644 ServiceStack/Host/RestHandler.cs create mode 100644 ServiceStack/Host/RestPath.cs create mode 100644 ServiceStack/Host/ServiceController.cs create mode 100644 ServiceStack/Host/ServiceExec.cs create mode 100644 ServiceStack/Host/ServiceMetadata.cs create mode 100644 ServiceStack/HttpHandlerFactory.cs create mode 100644 ServiceStack/HttpRequestExtensions.cs create mode 100644 ServiceStack/HttpResponseExtensionsInternal.cs create mode 100644 ServiceStack/HttpResult.cs create mode 100644 ServiceStack/HttpUtils.cs create mode 100644 ServiceStack/Properties/AssemblyInfo.cs create mode 100644 ServiceStack/ReflectionExtensions.cs create mode 100644 ServiceStack/ServiceStack.csproj create mode 100644 ServiceStack/ServiceStack.nuget.targets create mode 100644 ServiceStack/ServiceStack.xproj create mode 100644 ServiceStack/ServiceStackHost.Runtime.cs create mode 100644 ServiceStack/ServiceStackHost.cs create mode 100644 ServiceStack/StringMapTypeDeserializer.cs create mode 100644 ServiceStack/UrlExtensions.cs create mode 100644 ServiceStack/packages.config create mode 100644 ServiceStack/project.json (limited to 'ServiceStack') diff --git a/ServiceStack/FilterAttributeCache.cs b/ServiceStack/FilterAttributeCache.cs new file mode 100644 index 000000000..378433add --- /dev/null +++ b/ServiceStack/FilterAttributeCache.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using ServiceStack; + +namespace ServiceStack.Support.WebHost +{ + public static class FilterAttributeCache + { + public static MediaBrowser.Model.Services.IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType) + { + var attributes = requestDtoType.AllAttributes().OfType().ToList(); + + var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestDtoType); + if (serviceType != null) + { + attributes.AddRange(serviceType.AllAttributes().OfType()); + } + + attributes.Sort((x,y) => x.Priority - y.Priority); + + return attributes.ToArray(); + } + } +} diff --git a/ServiceStack/Host/ActionContext.cs b/ServiceStack/Host/ActionContext.cs new file mode 100644 index 000000000..9f165cff1 --- /dev/null +++ b/ServiceStack/Host/ActionContext.cs @@ -0,0 +1,27 @@ +using System; + +namespace ServiceStack.Host +{ + /// + /// Context to capture IService action + /// + public class ActionContext + { + public const string AnyAction = "ANY"; + + public string Id { get; set; } + + public ActionInvokerFn ServiceAction { get; set; } + public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; } + + public static string Key(Type serviceType, string method, string requestDtoName) + { + return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName; + } + + public static string AnyKey(Type serviceType, string requestDtoName) + { + return Key(serviceType, AnyAction, requestDtoName); + } + } +} \ No newline at end of file diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs new file mode 100644 index 000000000..22fdc3e50 --- /dev/null +++ b/ServiceStack/Host/ContentTypes.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using MediaBrowser.Model.Services; + +namespace ServiceStack.Host +{ + public class ContentTypes + { + public static ContentTypes Instance = new ContentTypes(); + + public void SerializeToStream(IRequest req, object response, Stream responseStream) + { + var contentType = req.ResponseContentType; + var serializer = GetResponseSerializer(contentType); + if (serializer == null) + throw new NotSupportedException("ContentType not supported: " + contentType); + + var httpRes = new HttpResponseStreamWrapper(responseStream, req) + { + Dto = req.Response.Dto + }; + serializer(req, response, httpRes); + } + + public Action GetResponseSerializer(string contentType) + { + var serializer = GetStreamSerializer(contentType); + if (serializer == null) return null; + + return (httpReq, dto, httpRes) => serializer(httpReq, dto, httpRes.OutputStream); + } + + public Action GetStreamSerializer(string contentType) + { + switch (GetRealContentType(contentType)) + { + case "application/xml": + case "text/xml": + case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml + return (r, o, s) => ServiceStackHost.Instance.SerializeToXml(o, s); + + case "application/json": + case "text/json": + return (r, o, s) => ServiceStackHost.Instance.SerializeToJson(o, s); + } + + return null; + } + + public Func GetStreamDeserializer(string contentType) + { + switch (GetRealContentType(contentType)) + { + case "application/xml": + case "text/xml": + case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml + return ServiceStackHost.Instance.DeserializeXml; + + case "application/json": + case "text/json": + return ServiceStackHost.Instance.DeserializeJson; + } + + return null; + } + + private static string GetRealContentType(string contentType) + { + return contentType == null + ? null + : contentType.Split(';')[0].ToLower().Trim(); + } + + } +} \ No newline at end of file diff --git a/ServiceStack/Host/HttpResponseStreamWrapper.cs b/ServiceStack/Host/HttpResponseStreamWrapper.cs new file mode 100644 index 000000000..33038da72 --- /dev/null +++ b/ServiceStack/Host/HttpResponseStreamWrapper.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using MediaBrowser.Model.Services; + +namespace ServiceStack.Host +{ + public class HttpResponseStreamWrapper : IHttpResponse + { + private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); + + public HttpResponseStreamWrapper(Stream stream, IRequest request) + { + this.OutputStream = stream; + this.Request = request; + this.Headers = new Dictionary(); + this.Items = new Dictionary(); + } + + public Dictionary Headers { get; set; } + + public object OriginalResponse + { + get { return null; } + } + + public IRequest Request { get; private set; } + + public int StatusCode { set; get; } + public string StatusDescription { set; get; } + public string ContentType { get; set; } + + public void AddHeader(string name, string value) + { + this.Headers[name] = value; + } + + public string GetHeader(string name) + { + return this.Headers[name]; + } + + public void Redirect(string url) + { + this.Headers["Location"] = url; + } + + public Stream OutputStream { get; private set; } + + public object Dto { get; set; } + + public void Write(string text) + { + var bytes = UTF8EncodingWithoutBom.GetBytes(text); + OutputStream.Write(bytes, 0, bytes.Length); + } + + public bool UseBufferedStream { get; set; } + + public void Close() + { + if (IsClosed) return; + + OutputStream.Dispose(); + IsClosed = true; + } + + public void End() + { + Close(); + } + + public void Flush() + { + OutputStream.Flush(); + } + + public bool IsClosed { get; private set; } + + public void SetContentLength(long contentLength) {} + + public bool KeepAlive { get; set; } + + public Dictionary Items { get; private set; } + + public void SetCookie(Cookie cookie) + { + } + + public void ClearCookies() + { + } + } +} \ No newline at end of file diff --git a/ServiceStack/Host/RestHandler.cs b/ServiceStack/Host/RestHandler.cs new file mode 100644 index 000000000..5c360d150 --- /dev/null +++ b/ServiceStack/Host/RestHandler.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace ServiceStack.Host +{ + public class RestHandler + { + public string RequestName { get; set; } + + public async Task HandleResponseAsync(object response) + { + var taskResponse = response as Task; + + if (taskResponse == null) + { + return response; + } + + await taskResponse.ConfigureAwait(false); + + var taskResult = ServiceStackHost.Instance.GetTaskResult(taskResponse, RequestName); + + var taskResults = taskResult as Task[]; + + if (taskResults == null) + { + var subTask = taskResult as Task; + if (subTask != null) + taskResult = ServiceStackHost.Instance.GetTaskResult(subTask, RequestName); + + return taskResult; + } + + if (taskResults.Length == 0) + { + return new object[] { }; + } + + var firstResponse = ServiceStackHost.Instance.GetTaskResult(taskResults[0], RequestName); + var batchedResponses = firstResponse != null + ? (object[])Array.CreateInstance(firstResponse.GetType(), taskResults.Length) + : new object[taskResults.Length]; + batchedResponses[0] = firstResponse; + for (var i = 1; i < taskResults.Length; i++) + { + batchedResponses[i] = ServiceStackHost.Instance.GetTaskResult(taskResults[i], RequestName); + } + return batchedResponses; + } + + protected static object CreateContentTypeRequest(IRequest httpReq, Type requestType, string contentType) + { + if (!string.IsNullOrEmpty(contentType) && httpReq.ContentLength > 0) + { + var deserializer = ContentTypes.Instance.GetStreamDeserializer(contentType); + if (deserializer != null) + { + return deserializer(requestType, httpReq.InputStream); + } + } + return ServiceStackHost.Instance.CreateInstance(requestType); //Return an empty DTO, even for empty request bodies + } + + protected static object GetCustomRequestFromBinder(IRequest httpReq, Type requestType) + { + Func requestFactoryFn; + ServiceStackHost.Instance.ServiceController.RequestTypeFactoryMap.TryGetValue( + requestType, out requestFactoryFn); + + return requestFactoryFn != null ? requestFactoryFn(httpReq) : null; + } + + public static RestPath FindMatchingRestPath(string httpMethod, string pathInfo, out string contentType) + { + pathInfo = GetSanitizedPathInfo(pathInfo, out contentType); + + return ServiceStackHost.Instance.ServiceController.GetRestPathForRequest(httpMethod, pathInfo); + } + + public static string GetSanitizedPathInfo(string pathInfo, out string contentType) + { + contentType = null; + var pos = pathInfo.LastIndexOf('.'); + if (pos >= 0) + { + var format = pathInfo.Substring(pos + 1); + contentType = GetFormatContentType(format); + if (contentType != null) + { + pathInfo = pathInfo.Substring(0, pos); + } + } + return pathInfo; + } + + private static string GetFormatContentType(string format) + { + //built-in formats + if (format == "json") + return "application/json"; + if (format == "xml") + return "application/xml"; + + return null; + } + + public RestPath GetRestPath(string httpMethod, string pathInfo) + { + if (this.RestPath == null) + { + string contentType; + this.RestPath = FindMatchingRestPath(httpMethod, pathInfo, out contentType); + + if (contentType != null) + ResponseContentType = contentType; + } + return this.RestPath; + } + + public RestPath RestPath { get; set; } + + // Set from SSHHF.GetHandlerForPathInfo() + public string ResponseContentType { get; set; } + + public async Task ProcessRequestAsync(IRequest httpReq, IResponse httpRes, string operationName) + { + var appHost = ServiceStackHost.Instance; + + var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo); + if (restPath == null) + { + throw new NotSupportedException("No RestPath found for: " + httpReq.Verb + " " + httpReq.PathInfo); + } + httpReq.SetRoute(restPath); + + if (ResponseContentType != null) + httpReq.ResponseContentType = ResponseContentType; + + var request = httpReq.Dto = CreateRequest(httpReq, restPath); + + if (appHost.ApplyRequestFilters(httpReq, httpRes, request)) + return; + + var rawResponse = await ServiceStackHost.Instance.ServiceController.Execute(request, httpReq).ConfigureAwait(false); + + if (httpRes.IsClosed) + return; + + var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false); + + if (appHost.ApplyResponseFilters(httpReq, httpRes, response)) + return; + + await httpRes.WriteToResponse(httpReq, response).ConfigureAwait(false); + } + + public static object CreateRequest(IRequest httpReq, RestPath restPath) + { + var dtoFromBinder = GetCustomRequestFromBinder(httpReq, restPath.RequestType); + if (dtoFromBinder != null) + return dtoFromBinder; + + var requestParams = httpReq.GetFlattenedRequestParams(); + return CreateRequest(httpReq, restPath, requestParams); + } + + public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary requestParams) + { + var requestDto = CreateContentTypeRequest(httpReq, restPath.RequestType, httpReq.ContentType); + + return CreateRequest(httpReq, restPath, requestParams, requestDto); + } + + public static object CreateRequest(IRequest httpReq, RestPath restPath, Dictionary requestParams, object requestDto) + { + string contentType; + var pathInfo = !restPath.IsWildCardPath + ? GetSanitizedPathInfo(httpReq.PathInfo, out contentType) + : httpReq.PathInfo; + + return restPath.CreateRequest(pathInfo, requestParams, requestDto); + } + + /// + /// Used in Unit tests + /// + /// + public object CreateRequest(IRequest httpReq, string operationName) + { + if (this.RestPath == null) + throw new ArgumentNullException("No RestPath found"); + + return CreateRequest(httpReq, this.RestPath); + } + } + +} diff --git a/ServiceStack/Host/RestPath.cs b/ServiceStack/Host/RestPath.cs new file mode 100644 index 000000000..7222578a9 --- /dev/null +++ b/ServiceStack/Host/RestPath.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using ServiceStack.Serialization; + +namespace ServiceStack.Host +{ + public class RestPath + { + private const string WildCard = "*"; + private const char WildCardChar = '*'; + private const string PathSeperator = "/"; + private const char PathSeperatorChar = '/'; + private const char ComponentSeperator = '.'; + private const string VariablePrefix = "{"; + + readonly bool[] componentsWithSeparators; + + private readonly string restPath; + private readonly string allowedVerbs; + private readonly bool allowsAllVerbs; + public bool IsWildCardPath { get; private set; } + + private readonly string[] literalsToMatch; + + private readonly string[] variablesNames; + + private readonly bool[] isWildcard; + private readonly int wildcardCount = 0; + + public int VariableArgsCount { get; set; } + + /// + /// The number of segments separated by '/' determinable by path.Split('/').Length + /// e.g. /path/to/here.ext == 3 + /// + public int PathComponentsCount { get; set; } + + /// + /// The total number of segments after subparts have been exploded ('.') + /// e.g. /path/to/here.ext == 4 + /// + public int TotalComponentsCount { get; set; } + + public string[] Verbs + { + get + { + return allowsAllVerbs + ? new[] { ActionContext.AnyAction } + : AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); + } + } + + public Type RequestType { get; private set; } + + public string Path { get { return this.restPath; } } + + public string Summary { get; private set; } + + public string Notes { get; private set; } + + public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } } + + public string AllowedVerbs { get { return this.allowedVerbs; } } + + public int Priority { get; set; } //passed back to RouteAttribute + + public static string[] GetPathPartsForMatching(string pathInfo) + { + var parts = pathInfo.ToLower().Split(PathSeperatorChar) + .Where(x => !string.IsNullOrEmpty(x)).ToArray(); + return parts; + } + + public static IEnumerable GetFirstMatchHashKeys(string[] pathPartsForMatching) + { + var hashPrefix = pathPartsForMatching.Length + PathSeperator; + return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching); + } + + public static IEnumerable GetFirstMatchWildCardHashKeys(string[] pathPartsForMatching) + { + const string hashPrefix = WildCard + PathSeperator; + return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching); + } + + private static IEnumerable GetPotentialMatchesWithPrefix(string hashPrefix, string[] pathPartsForMatching) + { + foreach (var part in pathPartsForMatching) + { + yield return hashPrefix + part; + var subParts = part.Split(ComponentSeperator); + if (subParts.Length == 1) continue; + + foreach (var subPart in subParts) + { + yield return hashPrefix + subPart; + } + } + } + + public RestPath(Type requestType, string path, string verbs, string summary = null, string notes = null) + { + this.RequestType = requestType; + this.Summary = summary; + this.Notes = notes; + this.restPath = path; + + this.allowsAllVerbs = verbs == null || verbs == WildCard; + if (!this.allowsAllVerbs) + { + this.allowedVerbs = verbs.ToUpper(); + } + + var componentsList = new List(); + + //We only split on '.' if the restPath has them. Allows for /{action}.{type} + var hasSeparators = new List(); + foreach (var component in this.restPath.Split(PathSeperatorChar)) + { + if (string.IsNullOrEmpty(component)) continue; + + if (component.Contains(VariablePrefix) + && component.IndexOf(ComponentSeperator) != -1) + { + hasSeparators.Add(true); + componentsList.AddRange(component.Split(ComponentSeperator)); + } + else + { + hasSeparators.Add(false); + componentsList.Add(component); + } + } + + var components = componentsList.ToArray(); + this.TotalComponentsCount = components.Length; + + this.literalsToMatch = new string[this.TotalComponentsCount]; + this.variablesNames = new string[this.TotalComponentsCount]; + this.isWildcard = new bool[this.TotalComponentsCount]; + this.componentsWithSeparators = hasSeparators.ToArray(); + this.PathComponentsCount = this.componentsWithSeparators.Length; + string firstLiteralMatch = null; + + var sbHashKey = new StringBuilder(); + for (var i = 0; i < components.Length; i++) + { + var component = components[i]; + + if (component.StartsWith(VariablePrefix)) + { + var variableName = component.Substring(1, component.Length - 2); + if (variableName[variableName.Length - 1] == WildCardChar) + { + this.isWildcard[i] = true; + variableName = variableName.Substring(0, variableName.Length - 1); + } + this.variablesNames[i] = variableName; + this.VariableArgsCount++; + } + else + { + this.literalsToMatch[i] = component.ToLower(); + sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch); + + if (firstLiteralMatch == null) + { + firstLiteralMatch = this.literalsToMatch[i]; + } + } + } + + for (var i = 0; i < components.Length - 1; i++) + { + if (!this.isWildcard[i]) continue; + if (this.literalsToMatch[i + 1] == null) + { + throw new ArgumentException( + "A wildcard path component must be at the end of the path or followed by a literal path component."); + } + } + + this.wildcardCount = this.isWildcard.Count(x => x); + this.IsWildCardPath = this.wildcardCount > 0; + + this.FirstMatchHashKey = !this.IsWildCardPath + ? this.PathComponentsCount + PathSeperator + firstLiteralMatch + : WildCardChar + PathSeperator + firstLiteralMatch; + + this.IsValid = sbHashKey.Length > 0; + this.UniqueMatchHashKey = sbHashKey.ToString(); + + this.typeDeserializer = new StringMapTypeDeserializer(this.RequestType); + RegisterCaseInsenstivePropertyNameMappings(); + } + + private void RegisterCaseInsenstivePropertyNameMappings() + { + foreach (var propertyInfo in RequestType.GetSerializableProperties()) + { + var propertyName = propertyInfo.Name; + propertyNamesMap.Add(propertyName.ToLower(), propertyName); + } + } + + public bool IsValid { get; set; } + + /// + /// Provide for quick lookups based on hashes that can be determined from a request url + /// + public string FirstMatchHashKey { get; private set; } + + public string UniqueMatchHashKey { get; private set; } + + private readonly StringMapTypeDeserializer typeDeserializer; + + private readonly Dictionary propertyNamesMap = new Dictionary(); + + public static Func CalculateMatchScore { get; set; } + + public int MatchScore(string httpMethod, string[] withPathInfoParts) + { + if (CalculateMatchScore != null) + return CalculateMatchScore(this, httpMethod, withPathInfoParts); + + int wildcardMatchCount; + var isMatch = IsMatch(httpMethod, withPathInfoParts, out wildcardMatchCount); + if (!isMatch) return -1; + + var score = 0; + + //Routes with least wildcard matches get the highest score + score += Math.Max((100 - wildcardMatchCount), 1) * 1000; + + //Routes with less variable (and more literal) matches + score += Math.Max((10 - VariableArgsCount), 1) * 100; + + //Exact verb match is better than ANY + var exactVerb = httpMethod == AllowedVerbs; + score += exactVerb ? 10 : 1; + + return score; + } + + /// + /// For performance withPathInfoParts should already be a lower case string + /// to minimize redundant matching operations. + /// + /// + /// + /// + /// + public bool IsMatch(string httpMethod, string[] withPathInfoParts, out int wildcardMatchCount) + { + wildcardMatchCount = 0; + + if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false; + if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false; + + if (!ExplodeComponents(ref withPathInfoParts)) return false; + if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false; + + int pathIx = 0; + for (var i = 0; i < this.TotalComponentsCount; i++) + { + if (this.isWildcard[i]) + { + if (i < this.TotalComponentsCount - 1) + { + // Continue to consume up until a match with the next literal + while (pathIx < withPathInfoParts.Length && withPathInfoParts[pathIx] != this.literalsToMatch[i + 1]) + { + pathIx++; + wildcardMatchCount++; + } + + // Ensure there are still enough parts left to match the remainder + if ((withPathInfoParts.Length - pathIx) < (this.TotalComponentsCount - i - 1)) + { + return false; + } + } + else + { + // A wildcard at the end matches the remainder of path + wildcardMatchCount += withPathInfoParts.Length - pathIx; + pathIx = withPathInfoParts.Length; + } + } + else + { + var literalToMatch = this.literalsToMatch[i]; + if (literalToMatch == null) + { + // Matching an ordinary (non-wildcard) variable consumes a single part + pathIx++; + continue; + } + + if (withPathInfoParts.Length <= pathIx || withPathInfoParts[pathIx] != literalToMatch) return false; + pathIx++; + } + } + + return pathIx == withPathInfoParts.Length; + } + + private bool ExplodeComponents(ref string[] withPathInfoParts) + { + var totalComponents = new List(); + for (var i = 0; i < withPathInfoParts.Length; i++) + { + var component = withPathInfoParts[i]; + if (string.IsNullOrEmpty(component)) continue; + + if (this.PathComponentsCount != this.TotalComponentsCount + && this.componentsWithSeparators[i]) + { + var subComponents = component.Split(ComponentSeperator); + if (subComponents.Length < 2) return false; + totalComponents.AddRange(subComponents); + } + else + { + totalComponents.Add(component); + } + } + + withPathInfoParts = totalComponents.ToArray(); + return true; + } + + public object CreateRequest(string pathInfo, Dictionary queryStringAndFormData, object fromInstance) + { + var requestComponents = pathInfo.Split(PathSeperatorChar) + .Where(x => !string.IsNullOrEmpty(x)).ToArray(); + + ExplodeComponents(ref requestComponents); + + if (requestComponents.Length != this.TotalComponentsCount) + { + var isValidWildCardPath = this.IsWildCardPath + && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount; + + if (!isValidWildCardPath) + throw new ArgumentException(string.Format( + "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'", + pathInfo, this.restPath)); + } + + var requestKeyValuesMap = new Dictionary(); + var pathIx = 0; + for (var i = 0; i < this.TotalComponentsCount; i++) + { + var variableName = this.variablesNames[i]; + if (variableName == null) + { + pathIx++; + continue; + } + + string propertyNameOnRequest; + if (!this.propertyNamesMap.TryGetValue(variableName.ToLower(), out propertyNameOnRequest)) + { + if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase)) + { + pathIx++; + continue; + } + + throw new ArgumentException("Could not find property " + + variableName + " on " + RequestType.GetOperationName()); + } + + var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch + if (value != null && this.isWildcard[i]) + { + if (i == this.TotalComponentsCount - 1) + { + // Wildcard at end of path definition consumes all the rest + var sb = new StringBuilder(); + sb.Append(value); + for (var j = pathIx + 1; j < requestComponents.Length; j++) + { + sb.Append(PathSeperatorChar + requestComponents[j]); + } + value = sb.ToString(); + } + else + { + // Wildcard in middle of path definition consumes up until it + // hits a match for the next element in the definition (which must be a literal) + // It may consume 0 or more path parts + var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1]; + if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) + { + var sb = new StringBuilder(); + sb.Append(value); + pathIx++; + while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) + { + sb.Append(PathSeperatorChar + requestComponents[pathIx++]); + } + value = sb.ToString(); + } + else + { + value = null; + } + } + } + else + { + // Variable consumes single path item + pathIx++; + } + + requestKeyValuesMap[propertyNameOnRequest] = value; + } + + if (queryStringAndFormData != null) + { + //Query String and form data can override variable path matches + //path variables < query string < form data + foreach (var name in queryStringAndFormData) + { + requestKeyValuesMap[name.Key] = name.Value; + } + } + + return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap); + } + + public override int GetHashCode() + { + return UniqueMatchHashKey.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/ServiceStack/Host/ServiceController.cs b/ServiceStack/Host/ServiceController.cs new file mode 100644 index 000000000..703f06365 --- /dev/null +++ b/ServiceStack/Host/ServiceController.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace ServiceStack.Host +{ + public delegate Task InstanceExecFn(IRequest requestContext, object intance, object request); + public delegate object ActionInvokerFn(object intance, object request); + public delegate void VoidActionInvokerFn(object intance, object request); + + public class ServiceController + { + private readonly Func> _resolveServicesFn; + + public ServiceController(Func> resolveServicesFn) + { + _resolveServicesFn = resolveServicesFn; + this.RequestTypeFactoryMap = new Dictionary>(); + } + + public Dictionary> RequestTypeFactoryMap { get; set; } + + public void Init() + { + foreach (var serviceType in _resolveServicesFn()) + { + RegisterService(serviceType); + } + } + + private Type[] GetGenericArguments(Type type) + { + return type.GetTypeInfo().IsGenericTypeDefinition + ? type.GetTypeInfo().GenericTypeParameters + : type.GetTypeInfo().GenericTypeArguments; + } + + public void RegisterService(Type serviceType) + { + var processedReqs = new HashSet(); + + var actions = ServiceExecGeneral.Reset(serviceType); + + var requiresRequestStreamTypeInfo = typeof(IRequiresRequestStream).GetTypeInfo(); + + var appHost = ServiceStackHost.Instance; + foreach (var mi in serviceType.GetActions()) + { + var requestType = mi.GetParameters()[0].ParameterType; + if (processedReqs.Contains(requestType)) continue; + processedReqs.Add(requestType); + + ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions); + + var returnMarker = requestType.GetTypeWithGenericTypeDefinitionOf(typeof(IReturn<>)); + var responseType = returnMarker != null ? + GetGenericArguments(returnMarker)[0] + : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ? + mi.ReturnType + : Type.GetType(requestType.FullName + "Response"); + + RegisterRestPaths(requestType); + + appHost.Metadata.Add(serviceType, requestType, responseType); + + if (requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo())) + { + this.RequestTypeFactoryMap[requestType] = req => + { + var restPath = req.GetRoute(); + var request = RestHandler.CreateRequest(req, restPath, req.GetRequestParams(), ServiceStackHost.Instance.CreateInstance(requestType)); + + var rawReq = (IRequiresRequestStream)request; + rawReq.RequestStream = req.InputStream; + return rawReq; + }; + } + } + } + + public readonly Dictionary> RestPathMap = new Dictionary>(); + + public void RegisterRestPaths(Type requestType) + { + var appHost = ServiceStackHost.Instance; + var attrs = appHost.GetRouteAttributes(requestType); + foreach (MediaBrowser.Model.Services.RouteAttribute attr in attrs) + { + var restPath = new RestPath(requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes); + + if (!restPath.IsValid) + throw new NotSupportedException(string.Format( + "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetOperationName())); + + RegisterRestPath(restPath); + } + } + + private static readonly char[] InvalidRouteChars = new[] { '?', '&' }; + + public void RegisterRestPath(RestPath restPath) + { + if (!restPath.Path.StartsWith("/")) + throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetOperationName())); + if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) + throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " + + "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetOperationName())); + + List pathsAtFirstMatch; + if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch)) + { + pathsAtFirstMatch = new List(); + RestPathMap[restPath.FirstMatchHashKey] = pathsAtFirstMatch; + } + pathsAtFirstMatch.Add(restPath); + } + + public void AfterInit() + { + var appHost = ServiceStackHost.Instance; + + //Register any routes configured on Metadata.Routes + foreach (var restPath in appHost.RestPaths) + { + RegisterRestPath(restPath); + } + + //Sync the RestPaths collections + appHost.RestPaths.Clear(); + appHost.RestPaths.AddRange(RestPathMap.Values.SelectMany(x => x)); + } + + public RestPath GetRestPathForRequest(string httpMethod, string pathInfo) + { + var matchUsingPathParts = RestPath.GetPathPartsForMatching(pathInfo); + + List firstMatches; + + var yieldedHashMatches = RestPath.GetFirstMatchHashKeys(matchUsingPathParts); + foreach (var potentialHashMatch in yieldedHashMatches) + { + if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue; + + var bestScore = -1; + foreach (var restPath in firstMatches) + { + var score = restPath.MatchScore(httpMethod, matchUsingPathParts); + if (score > bestScore) bestScore = score; + } + if (bestScore > 0) + { + foreach (var restPath in firstMatches) + { + if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts)) + return restPath; + } + } + } + + var yieldedWildcardMatches = RestPath.GetFirstMatchWildCardHashKeys(matchUsingPathParts); + foreach (var potentialHashMatch in yieldedWildcardMatches) + { + if (!this.RestPathMap.TryGetValue(potentialHashMatch, out firstMatches)) continue; + + var bestScore = -1; + foreach (var restPath in firstMatches) + { + var score = restPath.MatchScore(httpMethod, matchUsingPathParts); + if (score > bestScore) bestScore = score; + } + if (bestScore > 0) + { + foreach (var restPath in firstMatches) + { + if (bestScore == restPath.MatchScore(httpMethod, matchUsingPathParts)) + return restPath; + } + } + } + + return null; + } + + public async Task Execute(object requestDto, IRequest req) + { + req.Dto = requestDto; + var requestType = requestDto.GetType(); + req.OperationName = requestType.Name; + + var serviceType = ServiceStackHost.Instance.Metadata.GetServiceTypeByRequest(requestType); + + var service = ServiceStackHost.Instance.CreateInstance(serviceType); + + //var service = typeFactory.CreateInstance(serviceType); + + var serviceRequiresContext = service as IRequiresRequest; + if (serviceRequiresContext != null) + { + serviceRequiresContext.Request = req; + } + + if (req.Dto == null) // Don't override existing batched DTO[] + req.Dto = requestDto; + + //Executes the service and returns the result + var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false); + + if (req.Response.Dto == null) + req.Response.Dto = response; + + return response; + } + } + +} \ No newline at end of file diff --git a/ServiceStack/Host/ServiceExec.cs b/ServiceStack/Host/ServiceExec.cs new file mode 100644 index 000000000..cb501a3ad --- /dev/null +++ b/ServiceStack/Host/ServiceExec.cs @@ -0,0 +1,156 @@ +//Copyright (c) Service Stack LLC. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace ServiceStack.Host +{ + public static class ServiceExecExtensions + { + public static IEnumerable GetActions(this Type serviceType) + { + foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic)) + { + if (mi.GetParameters().Length != 1) + continue; + + var actionName = mi.Name.ToUpper(); + if (!HttpMethods.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction) + continue; + + yield return mi; + } + } + } + + internal static class ServiceExecGeneral + { + public static Dictionary execMap = new Dictionary(); + + public static void CreateServiceRunnersFor(Type requestType, List actions) + { + foreach (var actionCtx in actions) + { + if (execMap.ContainsKey(actionCtx.Id)) continue; + + execMap[actionCtx.Id] = actionCtx; + } + } + + public static async Task Execute(Type serviceType, IRequest request, object instance, object requestDto, string requestName) + { + var actionName = request.Verb + ?? HttpMethods.Post; //MQ Services + + ActionContext actionContext; + if (ServiceExecGeneral.execMap.TryGetValue(ActionContext.Key(serviceType, actionName, requestName), out actionContext) + || ServiceExecGeneral.execMap.TryGetValue(ActionContext.AnyKey(serviceType, requestName), out actionContext)) + { + if (actionContext.RequestFilters != null) + { + foreach (var requestFilter in actionContext.RequestFilters) + { + requestFilter.RequestFilter(request, request.Response, requestDto); + if (request.Response.IsClosed) return null; + } + } + + var response = actionContext.ServiceAction(instance, requestDto); + + var taskResponse = response as Task; + if (taskResponse != null) + { + await taskResponse.ConfigureAwait(false); + response = ServiceStackHost.Instance.GetTaskResult(taskResponse, requestName); + } + + return response; + } + + var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower(); + throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetOperationName(), expectedMethodName, serviceType.GetOperationName())); + } + + public static List Reset(Type serviceType) + { + var actions = new List(); + + foreach (var mi in serviceType.GetActions()) + { + var actionName = mi.Name.ToUpper(); + var args = mi.GetParameters(); + + var requestType = args[0].ParameterType; + var actionCtx = new ActionContext + { + Id = ActionContext.Key(serviceType, actionName, requestType.GetOperationName()) + }; + + try + { + actionCtx.ServiceAction = CreateExecFn(serviceType, requestType, mi); + } + catch + { + //Potential problems with MONO, using reflection for fallback + actionCtx.ServiceAction = (service, request) => + mi.Invoke(service, new[] { request }); + } + + var reqFilters = new List(); + + foreach (var attr in mi.GetCustomAttributes(true)) + { + var hasReqFilter = attr as IHasRequestFilter; + + if (hasReqFilter != null) + reqFilters.Add(hasReqFilter); + } + + if (reqFilters.Count > 0) + actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray(); + + actions.Add(actionCtx); + } + + return actions; + } + + private static ActionInvokerFn CreateExecFn(Type serviceType, Type requestType, MethodInfo mi) + { + var serviceParam = Expression.Parameter(typeof(object), "serviceObj"); + var serviceStrong = Expression.Convert(serviceParam, serviceType); + + var requestDtoParam = Expression.Parameter(typeof(object), "requestDto"); + var requestDtoStrong = Expression.Convert(requestDtoParam, requestType); + + Expression callExecute = Expression.Call( + serviceStrong, mi, requestDtoStrong); + + if (mi.ReturnType != typeof(void)) + { + var executeFunc = Expression.Lambda + (callExecute, serviceParam, requestDtoParam).Compile(); + + return executeFunc; + } + else + { + var executeFunc = Expression.Lambda + (callExecute, serviceParam, requestDtoParam).Compile(); + + return (service, request) => + { + executeFunc(service, request); + return null; + }; + } + } + } +} \ No newline at end of file diff --git a/ServiceStack/Host/ServiceMetadata.cs b/ServiceStack/Host/ServiceMetadata.cs new file mode 100644 index 000000000..240e6f32d --- /dev/null +++ b/ServiceStack/Host/ServiceMetadata.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace ServiceStack.Host +{ + public class ServiceMetadata + { + public ServiceMetadata() + { + this.OperationsMap = new Dictionary(); + } + + public Dictionary OperationsMap { get; protected set; } + + public void Add(Type serviceType, Type requestType, Type responseType) + { + this.OperationsMap[requestType] = serviceType; + } + + public Type GetServiceTypeByRequest(Type requestType) + { + Type serviceType; + OperationsMap.TryGetValue(requestType, out serviceType); + return serviceType; + } + } +} diff --git a/ServiceStack/HttpHandlerFactory.cs b/ServiceStack/HttpHandlerFactory.cs new file mode 100644 index 000000000..d48bfeb5f --- /dev/null +++ b/ServiceStack/HttpHandlerFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Services; +using ServiceStack.Host; + +namespace ServiceStack +{ + public class HttpHandlerFactory + { + // Entry point for HttpListener + public static RestHandler GetHandler(IHttpRequest httpReq) + { + var pathInfo = httpReq.PathInfo; + + var pathParts = pathInfo.TrimStart('/').Split('/'); + if (pathParts.Length == 0) return null; + + string contentType; + var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType); + if (restPath != null) + return new RestHandler { RestPath = restPath, RequestName = restPath.RequestType.GetOperationName(), ResponseContentType = contentType }; + + return null; + } + } +} \ No newline at end of file diff --git a/ServiceStack/HttpRequestExtensions.cs b/ServiceStack/HttpRequestExtensions.cs new file mode 100644 index 000000000..c34d62601 --- /dev/null +++ b/ServiceStack/HttpRequestExtensions.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Services; +using ServiceStack.Host; + +namespace ServiceStack +{ + public static class HttpRequestExtensions + { + /** + * + Input: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?q=item#fragment + + Some HttpRequest path and URL properties: + Request.ApplicationPath: /Cambia3 + Request.CurrentExecutionFilePath: /Cambia3/Temp/Test.aspx + Request.FilePath: /Cambia3/Temp/Test.aspx + Request.Path: /Cambia3/Temp/Test.aspx/path/info + Request.PathInfo: /path/info + Request.PhysicalApplicationPath: D:\Inetpub\wwwroot\CambiaWeb\Cambia3\ + Request.QueryString: /Cambia3/Temp/Test.aspx/path/info?query=arg + Request.Url.AbsolutePath: /Cambia3/Temp/Test.aspx/path/info + Request.Url.AbsoluteUri: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?query=arg + Request.Url.Fragment: + Request.Url.Host: localhost + Request.Url.LocalPath: /Cambia3/Temp/Test.aspx/path/info + Request.Url.PathAndQuery: /Cambia3/Temp/Test.aspx/path/info?query=arg + Request.Url.Port: 96 + Request.Url.Query: ?query=arg + Request.Url.Scheme: http + Request.Url.Segments: / + Cambia3/ + Temp/ + Test.aspx/ + path/ + info + * */ + + /// + /// Duplicate Params are given a unique key by appending a #1 suffix + /// + public static Dictionary GetRequestParams(this IRequest request) + { + var map = new Dictionary(); + + foreach (var name in request.QueryString.Keys) + { + if (name == null) continue; //thank you ASP.NET + + var values = request.QueryString.GetValues(name); + if (values.Length == 1) + { + map[name] = values[0]; + } + else + { + for (var i = 0; i < values.Length; i++) + { + map[name + (i == 0 ? "" : "#" + i)] = values[i]; + } + } + } + + if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put) + && request.FormData != null) + { + foreach (var name in request.FormData.Keys) + { + if (name == null) continue; //thank you ASP.NET + + var values = request.FormData.GetValues(name); + if (values.Length == 1) + { + map[name] = values[0]; + } + else + { + for (var i = 0; i < values.Length; i++) + { + map[name + (i == 0 ? "" : "#" + i)] = values[i]; + } + } + } + } + + return map; + } + + /// + /// Duplicate params have their values joined together in a comma-delimited string + /// + public static Dictionary GetFlattenedRequestParams(this IRequest request) + { + var map = new Dictionary(); + + foreach (var name in request.QueryString.Keys) + { + if (name == null) continue; //thank you ASP.NET + map[name] = request.QueryString[name]; + } + + if ((request.Verb == HttpMethods.Post || request.Verb == HttpMethods.Put) + && request.FormData != null) + { + foreach (var name in request.FormData.Keys) + { + if (name == null) continue; //thank you ASP.NET + map[name] = request.FormData[name]; + } + } + + return map; + } + + public static void SetRoute(this IRequest req, RestPath route) + { + req.Items["__route"] = route; + } + + public static RestPath GetRoute(this IRequest req) + { + object route; + req.Items.TryGetValue("__route", out route); + return route as RestPath; + } + } +} \ No newline at end of file diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs new file mode 100644 index 000000000..1195f63ca --- /dev/null +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -0,0 +1,237 @@ +//Copyright (c) Service Stack LLC. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using MediaBrowser.Model.Services; +using ServiceStack.Host; + +namespace ServiceStack +{ + public static class HttpResponseExtensionsInternal + { + public static async Task WriteToOutputStream(IResponse response, object result) + { + var asyncStreamWriter = result as IAsyncStreamWriter; + if (asyncStreamWriter != null) + { + await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + return true; + } + + var streamWriter = result as IStreamWriter; + if (streamWriter != null) + { + streamWriter.WriteTo(response.OutputStream); + return true; + } + + var stream = result as Stream; + if (stream != null) + { + WriteTo(stream, response.OutputStream); + return true; + } + + var bytes = result as byte[]; + if (bytes != null) + { + response.ContentType = "application/octet-stream"; + response.SetContentLength(bytes.Length); + + response.OutputStream.Write(bytes, 0, bytes.Length); + return true; + } + + return false; + } + + public static long WriteTo(Stream inStream, Stream outStream) + { + var memoryStream = inStream as MemoryStream; + if (memoryStream != null) + { + memoryStream.WriteTo(outStream); + return memoryStream.Position; + } + + var data = new byte[4096]; + long total = 0; + int bytesRead; + + while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0) + { + outStream.Write(data, 0, bytesRead); + total += bytesRead; + } + + return total; + } + + /// + /// End a ServiceStack Request with no content + /// + public static void EndRequestWithNoContent(this IResponse httpRes) + { + if (httpRes.StatusCode == (int)HttpStatusCode.OK) + { + httpRes.StatusCode = (int)HttpStatusCode.NoContent; + } + + httpRes.SetContentLength(0); + } + + public static Task WriteToResponse(this IResponse httpRes, MediaBrowser.Model.Services.IRequest httpReq, object result) + { + if (result == null) + { + httpRes.EndRequestWithNoContent(); + return Task.FromResult(true); + } + + var httpResult = result as IHttpResult; + if (httpResult != null) + { + httpResult.RequestContext = httpReq; + httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType; + var httpResSerializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); + return httpRes.WriteToResponse(httpResult, httpResSerializer, httpReq); + } + + var serializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); + return httpRes.WriteToResponse(result, serializer, httpReq); + } + + private static object GetDto(object response) + { + if (response == null) return null; + var httpResult = response as IHttpResult; + return httpResult != null ? httpResult.Response : response; + } + + /// + /// Writes to response. + /// Response headers are customizable by implementing IHasHeaders an returning Dictionary of Http headers. + /// + /// The response. + /// Whether or not it was implicity handled by ServiceStack's built-in handlers. + /// The default action. + /// The serialization context. + /// + public static async Task WriteToResponse(this IResponse response, object result, Action defaultAction, MediaBrowser.Model.Services.IRequest request) + { + var defaultContentType = request.ResponseContentType; + if (result == null) + { + response.EndRequestWithNoContent(); + return; + } + + var httpResult = result as IHttpResult; + if (httpResult != null) + { + if (httpResult.RequestContext == null) + httpResult.RequestContext = request; + + response.Dto = response.Dto ?? GetDto(httpResult); + + response.StatusCode = httpResult.Status; + response.StatusDescription = httpResult.StatusDescription ?? httpResult.StatusCode.ToString(); + if (string.IsNullOrEmpty(httpResult.ContentType)) + { + httpResult.ContentType = defaultContentType; + } + response.ContentType = httpResult.ContentType; + + if (httpResult.Cookies != null) + { + var httpRes = response as IHttpResponse; + if (httpRes != null) + { + foreach (var cookie in httpResult.Cookies) + { + httpRes.SetCookie(cookie); + } + } + } + } + else + { + response.Dto = result; + } + + /* Mono Error: Exception: Method not found: 'System.Web.HttpResponse.get_Headers' */ + var responseOptions = result as IHasHeaders; + if (responseOptions != null) + { + //Reserving options with keys in the format 'xx.xxx' (No Http headers contain a '.' so its a safe restriction) + const string reservedOptions = "."; + + foreach (var responseHeaders in responseOptions.Headers) + { + if (responseHeaders.Key.Contains(reservedOptions)) continue; + if (responseHeaders.Key == "Content-Length") + { + response.SetContentLength(long.Parse(responseHeaders.Value)); + continue; + } + + response.AddHeader(responseHeaders.Key, responseHeaders.Value); + } + } + + //ContentType='text/html' is the default for a HttpResponse + //Do not override if another has been set + if (response.ContentType == null || response.ContentType == "text/html") + { + response.ContentType = defaultContentType; + } + + if (new HashSet { "application/json", }.Contains(response.ContentType)) + { + response.ContentType += "; charset=utf-8"; + } + + var disposableResult = result as IDisposable; + var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); + if (writeToOutputStreamResult) + { + response.Flush(); //required for Compression + if (disposableResult != null) disposableResult.Dispose(); + return; + } + + if (httpResult != null) + result = httpResult.Response; + + var responseText = result as string; + if (responseText != null) + { + if (response.ContentType == null || response.ContentType == "text/html") + response.ContentType = defaultContentType; + response.Write(responseText); + + return; + } + + if (defaultAction == null) + { + throw new ArgumentNullException("defaultAction", String.Format( + "As result '{0}' is not a supported responseType, a defaultAction must be supplied", + (result != null ? result.GetType().GetOperationName() : ""))); + } + + + if (result != null) + defaultAction(request, result, response); + + if (disposableResult != null) + disposableResult.Dispose(); + } + + } +} diff --git a/ServiceStack/HttpResult.cs b/ServiceStack/HttpResult.cs new file mode 100644 index 000000000..23a5cdffb --- /dev/null +++ b/ServiceStack/HttpResult.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; +using ServiceStack.Host; + +namespace ServiceStack +{ + public class HttpResult + : IHttpResult, IAsyncStreamWriter + { + public HttpResult() + : this((object)null, null) + { + } + + public HttpResult(object response) + : this(response, null) + { + } + + public HttpResult(object response, string contentType) + : this(response, contentType, HttpStatusCode.OK) + { + } + + public HttpResult(HttpStatusCode statusCode, string statusDescription) + : this() + { + StatusCode = statusCode; + StatusDescription = statusDescription; + } + + public HttpResult(object response, HttpStatusCode statusCode) + : this(response, null, statusCode) + { } + + public HttpResult(object response, string contentType, HttpStatusCode statusCode) + { + this.Headers = new Dictionary(); + this.Cookies = new List(); + + this.Response = response; + this.ContentType = contentType; + this.StatusCode = statusCode; + } + + public HttpResult(Stream responseStream, string contentType) + : this(null, contentType, HttpStatusCode.OK) + { + this.ResponseStream = responseStream; + } + + public HttpResult(string responseText, string contentType) + : this(null, contentType, HttpStatusCode.OK) + { + this.ResponseText = responseText; + } + + public HttpResult(byte[] responseBytes, string contentType) + : this(null, contentType, HttpStatusCode.OK) + { + this.ResponseStream = new MemoryStream(responseBytes); + } + + public string ResponseText { get; private set; } + + public Stream ResponseStream { get; private set; } + + public string ContentType { get; set; } + + public IDictionary Headers { get; private set; } + + public List Cookies { get; private set; } + + public string ETag { get; set; } + + public TimeSpan? Age { get; set; } + + public TimeSpan? MaxAge { get; set; } + + public DateTime? Expires { get; set; } + + public DateTime? LastModified { get; set; } + + public Func ResultScope { get; set; } + + public string Location + { + set + { + if (StatusCode == HttpStatusCode.OK) + StatusCode = HttpStatusCode.Redirect; + + this.Headers["Location"] = value; + } + } + + public void SetPermanentCookie(string name, string value) + { + SetCookie(name, value, DateTime.UtcNow.AddYears(20), null); + } + + public void SetPermanentCookie(string name, string value, string path) + { + SetCookie(name, value, DateTime.UtcNow.AddYears(20), path); + } + + public void SetSessionCookie(string name, string value) + { + SetSessionCookie(name, value, null); + } + + public void SetSessionCookie(string name, string value, string path) + { + path = path ?? "/"; + this.Headers["Set-Cookie"] = string.Format("{0}={1};path=" + path, name, value); + } + + public void SetCookie(string name, string value, TimeSpan expiresIn, string path) + { + var expiresAt = DateTime.UtcNow.Add(expiresIn); + SetCookie(name, value, expiresAt, path); + } + + public void SetCookie(string name, string value, DateTime expiresAt, string path, bool secure = false, bool httpOnly = false) + { + path = path ?? "/"; + var cookie = string.Format("{0}={1};expires={2};path={3}", name, value, expiresAt.ToString("R"), path); + if (secure) + cookie += ";Secure"; + if (httpOnly) + cookie += ";HttpOnly"; + + this.Headers["Set-Cookie"] = cookie; + } + + public void DeleteCookie(string name) + { + var cookie = string.Format("{0}=;expires={1};path=/", name, DateTime.UtcNow.AddDays(-1).ToString("R")); + this.Headers["Set-Cookie"] = cookie; + } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + + public object Response { get; set; } + + public MediaBrowser.Model.Services.IRequest RequestContext { get; set; } + + public string View { get; set; } + + public string Template { get; set; } + + public int PaddingLength { get; set; } + + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) + { + try + { + await WriteToInternalAsync(responseStream, cancellationToken).ConfigureAwait(false); + responseStream.Flush(); + } + finally + { + DisposeStream(); + } + } + + public static Task WriteTo(Stream inStream, Stream outStream, CancellationToken cancellationToken) + { + var memoryStream = inStream as MemoryStream; + if (memoryStream != null) + { + memoryStream.WriteTo(outStream); + return Task.FromResult(true); + } + + return inStream.CopyToAsync(outStream, 81920, cancellationToken); + } + + public async Task WriteToInternalAsync(Stream responseStream, CancellationToken cancellationToken) + { + var response = RequestContext != null ? RequestContext.Response : null; + + if (this.ResponseStream != null) + { + if (response != null) + { + var ms = ResponseStream as MemoryStream; + if (ms != null) + { + response.SetContentLength(ms.Length); + + await ms.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); + return; + } + } + + await WriteTo(this.ResponseStream, responseStream, cancellationToken).ConfigureAwait(false); + return; + } + + if (this.ResponseText != null) + { + var bytes = Encoding.UTF8.GetBytes(this.ResponseText); + if (response != null) + response.SetContentLength(bytes.Length); + + await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + return; + } + + var bytesResponse = this.Response as byte[]; + if (bytesResponse != null) + { + if (response != null) + response.SetContentLength(bytesResponse.Length); + + await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false); + return; + } + + ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream); + } + + private void DisposeStream() + { + try + { + if (ResponseStream != null) + { + this.ResponseStream.Dispose(); + } + } + catch { /*ignore*/ } + } + } +} diff --git a/ServiceStack/HttpUtils.cs b/ServiceStack/HttpUtils.cs new file mode 100644 index 000000000..41d191d61 --- /dev/null +++ b/ServiceStack/HttpUtils.cs @@ -0,0 +1,34 @@ +//Copyright (c) Service Stack LLC. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using System; +using System.Collections.Generic; + +namespace ServiceStack +{ + internal static class HttpMethods + { + static readonly string[] allVerbs = new[] { + "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 + "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 + "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", + "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253 + "ORDERPATCH", // RFC 3648 + "ACL", // RFC 3744 + "PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/ + "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ + "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", + "POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx + }; + + public static HashSet AllVerbs = new HashSet(allVerbs); + + public const string Get = "GET"; + public const string Put = "PUT"; + public const string Post = "POST"; + public const string Delete = "DELETE"; + public const string Options = "OPTIONS"; + public const string Head = "HEAD"; + public const string Patch = "PATCH"; + } +} diff --git a/ServiceStack/Properties/AssemblyInfo.cs b/ServiceStack/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6073dc0b4 --- /dev/null +++ b/ServiceStack/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ServiceStack")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Service Stack LLC")] +[assembly: AssemblyProduct("ServiceStack")] +[assembly: AssemblyCopyright("Copyright (c) ServiceStack 2016")] +[assembly: AssemblyTrademark("Service Stack")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("06704d66-af8e-411f-8260-8d05de5ce6ad")] + +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] diff --git a/ServiceStack/ReflectionExtensions.cs b/ServiceStack/ReflectionExtensions.cs new file mode 100644 index 000000000..bbabd0dd7 --- /dev/null +++ b/ServiceStack/ReflectionExtensions.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace ServiceStack +{ + public static class ReflectionExtensions + { + public static bool IsInstanceOf(this Type type, Type thisOrBaseType) + { + while (type != null) + { + if (type == thisOrBaseType) + return true; + + type = type.BaseType(); + } + return false; + } + + public static Type FirstGenericType(this Type type) + { + while (type != null) + { + if (type.IsGeneric()) + return type; + + type = type.BaseType(); + } + return null; + } + + public static Type GetTypeWithGenericTypeDefinitionOf(this Type type, Type genericTypeDefinition) + { + foreach (var t in type.GetTypeInterfaces()) + { + if (t.IsGeneric() && t.GetGenericTypeDefinition() == genericTypeDefinition) + { + return t; + } + } + + var genericType = type.FirstGenericType(); + if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition) + { + return genericType; + } + + return null; + } + + public static PropertyInfo[] GetAllProperties(this Type type) + { + if (type.IsInterface()) + { + var propertyInfos = new List(); + + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + var subType = queue.Dequeue(); + foreach (var subInterface in subType.GetTypeInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + var typeProperties = subType.GetTypesProperties(); + + var newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); + + propertyInfos.InsertRange(0, newPropertyInfos); + } + + return propertyInfos.ToArray(); + } + + return type.GetTypesProperties() + .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties + .ToArray(); + } + + public static PropertyInfo[] GetPublicProperties(this Type type) + { + if (type.IsInterface()) + { + var propertyInfos = new List(); + + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + + while (queue.Count > 0) + { + var subType = queue.Dequeue(); + foreach (var subInterface in subType.GetTypeInterfaces()) + { + if (considered.Contains(subInterface)) continue; + + considered.Add(subInterface); + queue.Enqueue(subInterface); + } + + var typeProperties = subType.GetTypesPublicProperties(); + + var newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); + + propertyInfos.InsertRange(0, newPropertyInfos); + } + + return propertyInfos.ToArray(); + } + + return type.GetTypesPublicProperties() + .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties + .ToArray(); + } + + public const string DataMember = "DataMemberAttribute"; + + internal static string[] IgnoreAttributesNamed = new[] { + "IgnoreDataMemberAttribute", + "JsonIgnoreAttribute" + }; + + public static PropertyInfo[] GetSerializableProperties(this Type type) + { + var properties = type.IsDto() + ? type.GetAllProperties() + : type.GetPublicProperties(); + return properties.OnlySerializableProperties(type); + } + + + private static List _excludeTypes = new List { typeof(Stream) }; + + public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] properties, Type type = null) + { + var isDto = type.IsDto(); + var readableProperties = properties.Where(x => x.PropertyGetMethod(nonPublic: isDto) != null); + + if (isDto) + { + return readableProperties.Where(attr => + attr.HasAttribute()).ToArray(); + } + + // else return those properties that are not decorated with IgnoreDataMember + return readableProperties + .Where(prop => prop.AllAttributes() + .All(attr => + { + var name = attr.GetType().Name; + return !IgnoreAttributesNamed.Contains(name); + })) + .Where(prop => !_excludeTypes.Contains(prop.PropertyType)) + .ToArray(); + } + } + + public static class PlatformExtensions //Because WinRT is a POS + { + public static bool IsInterface(this Type type) + { + return type.GetTypeInfo().IsInterface; + } + + public static bool IsGeneric(this Type type) + { + return type.GetTypeInfo().IsGenericType; + } + + public static Type BaseType(this Type type) + { + return type.GetTypeInfo().BaseType; + } + + public static Type[] GetTypeInterfaces(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.ToArray(); + } + + internal static PropertyInfo[] GetTypesPublicProperties(this Type subType) + { + var pis = new List(); + foreach (var pi in subType.GetRuntimeProperties()) + { + var mi = pi.GetMethod ?? pi.SetMethod; + if (mi != null && mi.IsStatic) continue; + pis.Add(pi); + } + return pis.ToArray(); + } + + internal static PropertyInfo[] GetTypesProperties(this Type subType) + { + var pis = new List(); + foreach (var pi in subType.GetRuntimeProperties()) + { + var mi = pi.GetMethod ?? pi.SetMethod; + if (mi != null && mi.IsStatic) continue; + pis.Add(pi); + } + return pis.ToArray(); + } + + public static bool HasAttribute(this Type type) + { + return type.AllAttributes().Any(x => x.GetType() == typeof(T)); + } + + public static bool HasAttribute(this PropertyInfo pi) + { + return pi.AllAttributes().Any(x => x.GetType() == typeof(T)); + } + + public static bool IsDto(this Type type) + { + if (type == null) + return false; + + return type.HasAttribute(); + } + + public static MethodInfo PropertyGetMethod(this PropertyInfo pi, bool nonPublic = false) + { + return pi.GetMethod; + } + + public static object[] AllAttributes(this PropertyInfo propertyInfo) + { + return propertyInfo.GetCustomAttributes(true).ToArray(); + } + + public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrType) + { + return propertyInfo.GetCustomAttributes(true).Where(x => attrType.IsInstanceOf(x.GetType())).ToArray(); + } + + public static object[] AllAttributes(this Type type) + { + return type.GetTypeInfo().GetCustomAttributes(true).ToArray(); + } + + public static TAttr[] AllAttributes(this PropertyInfo pi) + { + return pi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + } + + public static TAttr[] AllAttributes(this Type type) + where TAttr : Attribute + { + return type.GetTypeInfo().GetCustomAttributes(true).ToArray(); + } + } +} diff --git a/ServiceStack/ServiceStack.csproj b/ServiceStack/ServiceStack.csproj new file mode 100644 index 000000000..3402339a6 --- /dev/null +++ b/ServiceStack/ServiceStack.csproj @@ -0,0 +1,131 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {680A1709-25EB-4D52-A87F-EE03FFD94BAA} + Library + Properties + ServiceStack + ServiceStack + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + 512 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + True + full + False + bin\Debug\ + TRACE;DEBUG;MONO + prompt + 4 + AllRules.ruleset + false + + + pdbonly + True + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + false + + + bin\Signed\ + TRACE + bin\Release\ServiceStack.XML + true + pdbonly + AnyCPU + prompt + AllRules.ruleset + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + \ No newline at end of file diff --git a/ServiceStack/ServiceStack.nuget.targets b/ServiceStack/ServiceStack.nuget.targets new file mode 100644 index 000000000..e69ce0e64 --- /dev/null +++ b/ServiceStack/ServiceStack.nuget.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ServiceStack/ServiceStack.xproj b/ServiceStack/ServiceStack.xproj new file mode 100644 index 000000000..ba8f8b8f2 --- /dev/null +++ b/ServiceStack/ServiceStack.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25420 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + b2d733ab-620e-4c53-88a4-4b6638ab6a7a + ServiceStack + .\obj + .\bin\ + + + + 2.0 + + + \ No newline at end of file diff --git a/ServiceStack/ServiceStackHost.Runtime.cs b/ServiceStack/ServiceStackHost.Runtime.cs new file mode 100644 index 000000000..1a1656a0e --- /dev/null +++ b/ServiceStack/ServiceStackHost.Runtime.cs @@ -0,0 +1,74 @@ +// Copyright (c) Service Stack LLC. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + + +using MediaBrowser.Model.Services; +using ServiceStack.Support.WebHost; + +namespace ServiceStack +{ + public abstract partial class ServiceStackHost + { + /// + /// Applies the request filters. Returns whether or not the request has been handled + /// and no more processing should be done. + /// + /// + public virtual bool ApplyRequestFilters(IRequest req, IResponse res, object requestDto) + { + if (res.IsClosed) return res.IsClosed; + + //Exec all RequestFilter attributes with Priority < 0 + var attributes = FilterAttributeCache.GetRequestFilterAttributes(requestDto.GetType()); + var i = 0; + for (; i < attributes.Length && attributes[i].Priority < 0; i++) + { + var attribute = attributes[i]; + attribute.RequestFilter(req, res, requestDto); + if (res.IsClosed) return res.IsClosed; + } + + if (res.IsClosed) return res.IsClosed; + + //Exec global filters + foreach (var requestFilter in GlobalRequestFilters) + { + requestFilter(req, res, requestDto); + if (res.IsClosed) return res.IsClosed; + } + + //Exec remaining RequestFilter attributes with Priority >= 0 + for (; i < attributes.Length && attributes[i].Priority >= 0; i++) + { + var attribute = attributes[i]; + attribute.RequestFilter(req, res, requestDto); + if (res.IsClosed) return res.IsClosed; + } + + return res.IsClosed; + } + + /// + /// Applies the response filters. Returns whether or not the request has been handled + /// and no more processing should be done. + /// + /// + public virtual bool ApplyResponseFilters(IRequest req, IResponse res, object response) + { + if (response != null) + { + if (res.IsClosed) return res.IsClosed; + } + + //Exec global filters + foreach (var responseFilter in GlobalResponseFilters) + { + responseFilter(req, res, response); + if (res.IsClosed) return res.IsClosed; + } + + return res.IsClosed; + } + } + +} \ No newline at end of file diff --git a/ServiceStack/ServiceStackHost.cs b/ServiceStack/ServiceStackHost.cs new file mode 100644 index 000000000..8a1db25e4 --- /dev/null +++ b/ServiceStack/ServiceStackHost.cs @@ -0,0 +1,104 @@ +// Copyright (c) Service Stack LLC. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; +using ServiceStack.Host; + +namespace ServiceStack +{ + public abstract partial class ServiceStackHost : IDisposable + { + public static ServiceStackHost Instance { get; protected set; } + + protected ServiceStackHost(string serviceName) + { + ServiceName = serviceName; + ServiceController = CreateServiceController(); + + RestPaths = new List(); + Metadata = new ServiceMetadata(); + GlobalRequestFilters = new List>(); + GlobalResponseFilters = new List>(); + } + + public abstract void Configure(); + + public abstract object CreateInstance(Type type); + + protected abstract ServiceController CreateServiceController(); + + public virtual ServiceStackHost Init() + { + Instance = this; + + ServiceController.Init(); + Configure(); + + ServiceController.AfterInit(); + + return this; + } + + public virtual ServiceStackHost Start(string urlBase) + { + throw new NotImplementedException("Start(listeningAtUrlBase) is not supported by this AppHost"); + } + + public string ServiceName { get; set; } + + public ServiceMetadata Metadata { get; set; } + + public ServiceController ServiceController { get; set; } + + public List RestPaths = new List(); + + public List> GlobalRequestFilters { get; set; } + + public List> GlobalResponseFilters { get; set; } + + public abstract T TryResolve(); + public abstract T Resolve(); + + public virtual MediaBrowser.Model.Services.RouteAttribute[] GetRouteAttributes(Type requestType) + { + return requestType.AllAttributes(); + } + + public abstract object GetTaskResult(Task task, string requestName); + + public abstract Func GetParseFn(Type propertyType); + + public abstract void SerializeToJson(object o, Stream stream); + public abstract void SerializeToXml(object o, Stream stream); + public abstract object DeserializeXml(Type type, Stream stream); + public abstract object DeserializeJson(Type type, Stream stream); + + public virtual void Dispose() + { + //JsConfig.Reset(); //Clears Runtime Attributes + + Instance = null; + } + + protected abstract ILogger Logger + { + get; + } + + public void OnLogError(Type type, string message) + { + Logger.Error(message); + } + + public void OnLogError(Type type, string message, Exception ex) + { + Logger.ErrorException(message, ex); + } + } +} diff --git a/ServiceStack/StringMapTypeDeserializer.cs b/ServiceStack/StringMapTypeDeserializer.cs new file mode 100644 index 000000000..762e8aaff --- /dev/null +++ b/ServiceStack/StringMapTypeDeserializer.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Linq; +using System.Reflection; + +namespace ServiceStack.Serialization +{ + /// + /// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls) + /// + public class StringMapTypeDeserializer + { + internal class PropertySerializerEntry + { + public PropertySerializerEntry(Action propertySetFn, Func propertyParseStringFn) + { + PropertySetFn = propertySetFn; + PropertyParseStringFn = propertyParseStringFn; + } + + public Action PropertySetFn; + public Func PropertyParseStringFn; + public Type PropertyType; + } + + private readonly Type type; + private readonly Dictionary propertySetterMap + = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public Func GetParseFn(Type propertyType) + { + //Don't JSV-decode string values for string properties + if (propertyType == typeof(string)) + return s => s; + + return ServiceStackHost.Instance.GetParseFn(propertyType); + } + + public StringMapTypeDeserializer(Type type) + { + this.type = type; + + foreach (var propertyInfo in type.GetSerializableProperties()) + { + var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); + var propertyType = propertyInfo.PropertyType; + var propertyParseStringFn = GetParseFn(propertyType); + var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType }; + + var attr = propertyInfo.AllAttributes().FirstOrDefault(); + if (attr != null && attr.Name != null) + { + propertySetterMap[attr.Name] = propertySerializer; + } + propertySetterMap[propertyInfo.Name] = propertySerializer; + } + } + + public object PopulateFromMap(object instance, IDictionary keyValuePairs) + { + string propertyName = null; + string propertyTextValue = null; + PropertySerializerEntry propertySerializerEntry = null; + + if (instance == null) + instance = ServiceStackHost.Instance.CreateInstance(type); + + foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value))) + { + propertyName = pair.Key; + propertyTextValue = pair.Value; + + if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) + { + if (propertyName == "v") + { + continue; + } + + continue; + } + + if (propertySerializerEntry.PropertySetFn == null) + { + continue; + } + + if (propertySerializerEntry.PropertyType == typeof(bool)) + { + //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value + propertyTextValue = LeftPart(propertyTextValue, ','); + } + + var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue); + if (value == null) + { + continue; + } + propertySerializerEntry.PropertySetFn(instance, value); + } + + return instance; + } + + public static string LeftPart(string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + } + + internal class TypeAccessor + { + public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) + { + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null; + + var setMethodInfo = propertyInfo.SetMethod; + return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); + } + } +} diff --git a/ServiceStack/UrlExtensions.cs b/ServiceStack/UrlExtensions.cs new file mode 100644 index 000000000..7b5a50ef1 --- /dev/null +++ b/ServiceStack/UrlExtensions.cs @@ -0,0 +1,33 @@ +using System; + +namespace ServiceStack +{ + /// + /// Donated by Ivan Korneliuk from his post: + /// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html + /// + /// Modified to only allow using routes matching the supplied HTTP Verb + /// + public static class UrlExtensions + { + public static string GetOperationName(this Type type) + { + var typeName = type.FullName != null //can be null, e.g. generic types + ? LeftPart(type.FullName, "[[") //Generic Fullname + .Replace(type.Namespace + ".", "") //Trim Namespaces + .Replace("+", ".") //Convert nested into normal type + : type.Name; + + return type.IsGenericParameter ? "'" + typeName : typeName; + } + + public static string LeftPart(string strVal, string needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + } +} \ No newline at end of file diff --git a/ServiceStack/packages.config b/ServiceStack/packages.config new file mode 100644 index 000000000..6b8deb9c9 --- /dev/null +++ b/ServiceStack/packages.config @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ServiceStack/project.json b/ServiceStack/project.json new file mode 100644 index 000000000..fbbe9eaf3 --- /dev/null +++ b/ServiceStack/project.json @@ -0,0 +1,17 @@ +{ + "frameworks":{ + "netstandard1.6":{ + "dependencies":{ + "NETStandard.Library":"1.6.0", + } + }, + ".NETPortable,Version=v4.5,Profile=Profile7":{ + "buildOptions": { + "define": [ ] + }, + "frameworkAssemblies":{ + + } + } + } +} \ No newline at end of file -- cgit v1.2.3 From 47552145b6240c2daad12f4c85d9c5465161fc3c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Nov 2016 15:21:40 -0500 Subject: update .net core solution --- Emby.Server.Core/project.json | 124 +++++++++++++++++++------------------- Emby.Server.sln | 76 +++++++++++++++++++++++ ServiceStack/ServiceStack.xproj | 19 ------ src/Emby.Server/Emby.Server.xproj | 3 + src/Emby.Server/project.json | 9 +++ 5 files changed, 150 insertions(+), 81 deletions(-) delete mode 100644 ServiceStack/ServiceStack.xproj (limited to 'ServiceStack') diff --git a/Emby.Server.Core/project.json b/Emby.Server.Core/project.json index 93528bb99..1a8e3432c 100644 --- a/Emby.Server.Core/project.json +++ b/Emby.Server.Core/project.json @@ -62,68 +62,68 @@ "SocketHttpListener.Portable": { "target": "project" } - }, - "netstandard1.6": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.6.0", - "System.AppDomain": "2.0.11", - "System.Globalization.Extensions": "4.0.1", - "System.IO.FileSystem.Watcher": "4.0.0", - "System.Net.Security": "4.0.0", - "System.Security.Cryptography.X509Certificates": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "MediaBrowser.Model": { - "target": "project" - }, - "MediaBrowser.Common": { - "target": "project" - }, - "MediaBrowser.Controller": { - "target": "project" - }, - "Emby.Common.Implementations": { - "target": "project" - }, - "Mono.Nat": { - "target": "project" - }, - "Emby.Server.Implementations": { - "target": "project" - }, - "MediaBrowser.Server.Implementations": { - "target": "project" - }, - "Emby.Dlna": { - "target": "project" - }, - "Emby.Photos": { - "target": "project" - }, - "MediaBrowser.Api": { - "target": "project" - }, - "MediaBrowser.MediaEncoding": { - "target": "project" - }, - "MediaBrowser.XbmcMetadata": { - "target": "project" - }, - "MediaBrowser.LocalMetadata": { - "target": "project" - }, - "MediaBrowser.WebDashboard": { - "target": "project" - }, - "Emby.Drawing": { - "target": "project" - }, - "SocketHttpListener.Portable": { - "target": "project" - }, - "ServiceStack": { - "target": "project" - } + } + }, + "netstandard1.6": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.AppDomain": "2.0.11", + "System.Globalization.Extensions": "4.0.1", + "System.IO.FileSystem.Watcher": "4.0.0", + "System.Net.Security": "4.0.0", + "System.Security.Cryptography.X509Certificates": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "MediaBrowser.Model": { + "target": "project" + }, + "MediaBrowser.Common": { + "target": "project" + }, + "MediaBrowser.Controller": { + "target": "project" + }, + "Emby.Common.Implementations": { + "target": "project" + }, + "Mono.Nat": { + "target": "project" + }, + "Emby.Server.Implementations": { + "target": "project" + }, + "MediaBrowser.Server.Implementations": { + "target": "project" + }, + "Emby.Dlna": { + "target": "project" + }, + "Emby.Photos": { + "target": "project" + }, + "MediaBrowser.Api": { + "target": "project" + }, + "MediaBrowser.MediaEncoding": { + "target": "project" + }, + "MediaBrowser.XbmcMetadata": { + "target": "project" + }, + "MediaBrowser.LocalMetadata": { + "target": "project" + }, + "MediaBrowser.WebDashboard": { + "target": "project" + }, + "Emby.Drawing": { + "target": "project" + }, + "SocketHttpListener.Portable": { + "target": "project" + }, + "ServiceStack": { + "target": "project" } } } diff --git a/Emby.Server.sln b/Emby.Server.sln index 70527efc1..c6e057c42 100644 --- a/Emby.Server.sln +++ b/Emby.Server.sln @@ -52,11 +52,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Implementations EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Server.Core", "Emby.Server.Core\Emby.Server.Core.xproj", "{65AA7D67-8059-40CD-91F1-16D02687226C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack", "ServiceStack\ServiceStack.csproj", "{680A1709-25EB-4D52-A87F-EE03FFD94BAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Portable", "SocketHttpListener.Portable\SocketHttpListener.Portable.csproj", "{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release Mono|Any CPU = Release Mono|Any CPU Release|Any CPU = Release|Any CPU + Signed|Any CPU = Signed|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {DDAFF431-0B3D-4857-8762-990A32DC8472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -65,126 +72,192 @@ Global {DDAFF431-0B3D-4857-8762-990A32DC8472}.Release Mono|Any CPU.Build.0 = Release|Any CPU {DDAFF431-0B3D-4857-8762-990A32DC8472}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDAFF431-0B3D-4857-8762-990A32DC8472}.Release|Any CPU.Build.0 = Release|Any CPU + {DDAFF431-0B3D-4857-8762-990A32DC8472}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {DDAFF431-0B3D-4857-8762-990A32DC8472}.Signed|Any CPU.Build.0 = Release|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release Mono|Any CPU.Build.0 = Release|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Signed|Any CPU.Build.0 = Release|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Signed|Any CPU.Build.0 = Release|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Signed|Any CPU.Build.0 = Release|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Release Mono|Any CPU.Build.0 = Release|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Signed|Any CPU.Build.0 = Release|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release Mono|Any CPU.Build.0 = Release|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Signed|Any CPU.Build.0 = Release|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Signed|Any CPU.Build.0 = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release Mono|Any CPU.Build.0 = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Signed|Any CPU.Build.0 = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release Mono|Any CPU.Build.0 = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Signed|Any CPU.Build.0 = Release|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Signed|Any CPU.Build.0 = Release|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Signed|Any CPU.Build.0 = Release|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Release Mono|Any CPU.Build.0 = Release|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A27010A-09C6-4E86-93EA-437484C10917}.Release|Any CPU.Build.0 = Release|Any CPU + {5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {5A27010A-09C6-4E86-93EA-437484C10917}.Signed|Any CPU.Build.0 = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release Mono|Any CPU.Build.0 = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Signed|Any CPU.Build.0 = Release|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Signed|Any CPU.Build.0 = Release|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.ActiveCfg = Release|Any CPU {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.Build.0 = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.Build.0 = Release|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.Build.0 = Release|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Signed|Any CPU.Build.0 = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.Build.0 = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Signed|Any CPU.Build.0 = Release|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.Build.0 = Release|Any CPU + {2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {2E781478-814D-4A48-9D80-BFF206441A65}.Signed|Any CPU.Build.0 = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release Mono|Any CPU.Build.0 = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|Any CPU.Build.0 = Release|Any CPU + {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Signed|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release Mono|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Signed|Any CPU.Build.0 = Release|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Debug|Any CPU.Build.0 = Debug|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Release Mono|Any CPU.Build.0 = Release|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Release|Any CPU.ActiveCfg = Release|Any CPU {65AA7D67-8059-40CD-91F1-16D02687226C}.Release|Any CPU.Build.0 = Release|Any CPU + {65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {65AA7D67-8059-40CD-91F1-16D02687226C}.Signed|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Signed|Any CPU.Build.0 = Release|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Any CPU.ActiveCfg = Signed|Any CPU + {680A1709-25EB-4D52-A87F-EE03FFD94BAA}.Signed|Any CPU.Build.0 = Signed|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Release|Any CPU.Build.0 = Release|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -211,5 +284,8 @@ Global {4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} {E383961B-9356-4D5D-8233-9A1079D03055} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} {65AA7D67-8059-40CD-91F1-16D02687226C} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} + {08FFF49B-F175-4807-A2B5-73B0EBD9F716} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} + {680A1709-25EB-4D52-A87F-EE03FFD94BAA} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} + {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E} = {8ADD772F-F0A4-4A53-9B2F-AF4A4C585839} EndGlobalSection EndGlobal diff --git a/ServiceStack/ServiceStack.xproj b/ServiceStack/ServiceStack.xproj deleted file mode 100644 index ba8f8b8f2..000000000 --- a/ServiceStack/ServiceStack.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0.25420 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - b2d733ab-620e-4c53-88a4-4b6638ab6a7a - ServiceStack - .\obj - .\bin\ - - - - 2.0 - - - \ No newline at end of file diff --git a/src/Emby.Server/Emby.Server.xproj b/src/Emby.Server/Emby.Server.xproj index ed4aa7734..6a23809ce 100644 --- a/src/Emby.Server/Emby.Server.xproj +++ b/src/Emby.Server/Emby.Server.xproj @@ -19,6 +19,7 @@ + @@ -33,6 +34,8 @@ + + \ No newline at end of file diff --git a/src/Emby.Server/project.json b/src/Emby.Server/project.json index dd70bc9bc..2693435a4 100644 --- a/src/Emby.Server/project.json +++ b/src/Emby.Server/project.json @@ -27,6 +27,9 @@ "Emby.Dlna": { "target": "project" }, + "Emby.Drawing": { + "target": "project" + }, "Emby.Photos": { "target": "project" }, @@ -68,6 +71,12 @@ }, "RSSDP": { "target": "project" + }, + "ServiceStack": { + "target": "project" + }, + "SocketHttpListener.Portable": { + "target": "project" } } } -- cgit v1.2.3 From c035f2baa1f3537d298a6559d15bd7ca40188e5d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 01:58:50 -0500 Subject: update responses --- Emby.Server.Core/ApplicationHost.cs | 8 +- .../HttpServer/HttpListenerHost.cs | 17 +- .../HttpServer/HttpResultFactory.cs | 91 ++++------ .../SocketSharp/WebSocketSharpResponse.cs | 7 - .../HttpServer/StreamWriter.cs | 12 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + .../Playback/StaticRemoteStreamWriter.cs | 12 +- MediaBrowser.Api/TestService.cs | 77 ++++++++ MediaBrowser.Model/Services/IHttpResult.cs | 5 - MediaBrowser.Model/Services/IRequest.cs | 16 -- ServiceStack/Host/ContentTypes.cs | 20 +-- ServiceStack/Host/HttpResponseStreamWrapper.cs | 95 ---------- ServiceStack/Host/ServiceController.cs | 3 - ServiceStack/HttpResponseExtensionsInternal.cs | 90 ++-------- ServiceStack/HttpResult.cs | 193 +-------------------- ServiceStack/ServiceStack.csproj | 1 - .../Net/EndPointListener.cs | 3 +- SocketHttpListener.Portable/Net/HttpConnection.cs | 10 +- SocketHttpListener.Portable/Net/HttpListener.cs | 11 -- .../Net/HttpListenerContext.cs | 1 - SocketHttpListener.Portable/Net/ResponseStream.cs | 37 +--- 21 files changed, 174 insertions(+), 536 deletions(-) create mode 100644 MediaBrowser.Api/TestService.cs delete mode 100644 ServiceStack/Host/HttpResponseStreamWrapper.cs (limited to 'ServiceStack') diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 9e0aee325..0c0ef894e 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -554,7 +554,7 @@ namespace Emby.Server.Core ZipClient = new ZipClient(FileSystemManager); RegisterSingleInstance(ZipClient); - RegisterSingleInstance(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, XmlSerializer)); + RegisterSingleInstance(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, MemoryStreamFactory)); RegisterSingleInstance(this); RegisterSingleInstance(ApplicationPaths); @@ -614,6 +614,9 @@ namespace Emby.Server.Core RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); + CertificatePath = GetCertificatePath(true); + Certificate = GetCertificate(CertificatePath); + HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); RegisterSingleInstance(HttpServer, false); @@ -995,9 +998,6 @@ namespace Emby.Server.Core /// private void StartServer() { - CertificatePath = GetCertificatePath(true); - Certificate = GetCertificate(CertificatePath); - try { ServerManager.Start(GetUrlPrefixes()); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 64f498b12..49c664eec 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -86,9 +86,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public override void Configure() - { - var mapExceptionToStatusCode = new Dictionary + readonly Dictionary _mapExceptionToStatusCode = new Dictionary { {typeof (InvalidOperationException), 500}, {typeof (NotImplementedException), 500}, @@ -102,6 +100,8 @@ namespace Emby.Server.Implementations.HttpServer {typeof (NotSupportedException), 500} }; + public override void Configure() + { var requestFilters = _appHost.GetExports().ToList(); foreach (var filter in requestFilters) { @@ -240,7 +240,12 @@ namespace Emby.Server.Implementations.HttpServer return; } - httpRes.StatusCode = 500; + int statusCode; + if (!_mapExceptionToStatusCode.TryGetValue(ex.GetType(), out statusCode)) + { + statusCode = 500; + } + httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; httpRes.Write(ex.Message); @@ -518,6 +523,10 @@ namespace Emby.Server.Implementations.HttpServer { await handler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); } + else + { + ErrorHandler(new FileNotFoundException(), httpReq); + } } catch (Exception ex) { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index bbd556661..f65331ec7 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,19 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IXmlSerializer _xmlSerializer; + private readonly IMemoryStreamFactory _memoryStreamFactory; /// /// Initializes a new instance of the class. /// - /// The log manager. - /// The file system. - /// The json serializer. - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _xmlSerializer = xmlSerializer; + _memoryStreamFactory = memoryStreamFactory; _logger = logManager.GetLogger("HttpResultFactory"); } @@ -59,17 +56,13 @@ namespace Emby.Server.Implementations.HttpServer /// System.Object. public object GetResult(object content, string contentType, IDictionary responseHeaders = null) { - return GetHttpResult(content, contentType, responseHeaders); + return GetHttpResult(content, contentType, true, responseHeaders); } /// /// Gets the HTTP result. /// - /// The content. - /// Type of the content. - /// The response headers. - /// IHasHeaders. - private IHasHeaders GetHttpResult(object content, string contentType, IDictionary responseHeaders = null) + private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary responseHeaders = null) { IHasHeaders result; @@ -98,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer } else { - result = new HttpResult(content, contentType); + result = new HttpResult(content, contentType, HttpStatusCode.OK); } } } @@ -107,7 +100,11 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - responseHeaders["Expires"] = "-1"; + if (addCachePrevention) + { + responseHeaders["Expires"] = "-1"; + } + AddResponseHeaders(result, responseHeaders); return result; @@ -184,8 +181,6 @@ namespace Emby.Server.Implementations.HttpServer /// public object ToOptimizedResult(IRequest request, T dto) { - request.Response.Dto = dto; - var compressionType = GetCompressionType(request); if (compressionType == null) { @@ -204,6 +199,7 @@ namespace Emby.Server.Implementations.HttpServer } } + // Do not use the memoryStreamFactory here, they don't place nice with compression using (var ms = new MemoryStream()) { using (var compressionStream = GetCompressionStream(ms, compressionType)) @@ -213,12 +209,9 @@ namespace Emby.Server.Implementations.HttpServer var compressedBytes = ms.ToArray(); - var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) - { - Status = request.Response.StatusCode - }; + var httpResult = new StreamWriter(compressedBytes, request.ResponseContentType, _logger); - httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); + //httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); httpResult.Headers["Content-Encoding"] = compressionType; return httpResult; @@ -226,6 +219,16 @@ namespace Emby.Server.Implementations.HttpServer } } + private static Stream GetCompressionStream(Stream outputStream, string compressionType) + { + if (compressionType == "deflate") + return new DeflateStream(outputStream, CompressionMode.Compress, true); + if (compressionType == "gzip") + return new GZipStream(outputStream, CompressionMode.Compress, true); + + throw new NotSupportedException(compressionType); + } + public static string GetRealContentType(string contentType) { return contentType == null @@ -233,7 +236,7 @@ namespace Emby.Server.Implementations.HttpServer : contentType.Split(';')[0].ToLower().Trim(); } - public static string SerializeToXmlString(object from) + private string SerializeToXmlString(object from) { using (var ms = new MemoryStream()) { @@ -253,16 +256,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress); - - throw new NotSupportedException(compressionType); - } - /// /// Gets the optimized result using cache. /// @@ -358,23 +351,7 @@ namespace Emby.Server.Implementations.HttpServer return hasHeaders; } - IHasHeaders httpResult; - - var stream = result as Stream; - - if (stream != null) - { - httpResult = new StreamWriter(stream, contentType, _logger); - } - else - { - // Otherwise wrap into an HttpResult - httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified); - } - - AddResponseHeaders(httpResult, responseHeaders); - - return httpResult; + return GetHttpResult(result, contentType, false, responseHeaders); } /// @@ -603,7 +580,7 @@ namespace Emby.Server.Implementations.HttpServer { stream.Dispose(); - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } return new StreamWriter(stream, contentType, _logger) @@ -630,13 +607,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } - return GetHttpResult(contents, contentType, responseHeaders); + return GetHttpResult(contents, contentType, true, responseHeaders); } - public static byte[] Compress(string text, string compressionType) + private byte[] Compress(string text, string compressionType) { if (compressionType == "deflate") return Deflate(text); @@ -647,12 +624,12 @@ namespace Emby.Server.Implementations.HttpServer throw new NotSupportedException(compressionType); } - public static byte[] Deflate(string text) + private byte[] Deflate(string text) { return Deflate(Encoding.UTF8.GetBytes(text)); } - public static byte[] Deflate(byte[] bytes) + private byte[] Deflate(byte[] bytes) { // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream @@ -666,12 +643,12 @@ namespace Emby.Server.Implementations.HttpServer } } - public static byte[] GZip(string text) + private byte[] GZip(string text) { return GZip(Encoding.UTF8.GetBytes(text)); } - public static byte[] GZip(byte[] buffer) + private byte[] GZip(byte[] buffer) { using (var ms = new MemoryStream()) using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index de0b33fe3..9de86e9cc 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -77,8 +77,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return _response.OutputStream; } } - public object Dto { get; set; } - public void Write(string text) { var bOutput = System.Text.Encoding.UTF8.GetBytes(text); @@ -120,11 +118,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } } - public void End() - { - Close(); - } - public void Flush() { _response.OutputStream.Flush(); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 15488abaa..33378949c 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -25,6 +25,8 @@ namespace Emby.Server.Implementations.HttpServer /// The source stream. private Stream SourceStream { get; set; } + private byte[] SourceBytes { get; set; } + /// /// The _options /// @@ -40,7 +42,6 @@ namespace Emby.Server.Implementations.HttpServer public Action OnComplete { get; set; } public Action OnError { get; set; } - private readonly byte[] _bytes; /// /// Initializes a new instance of the class. @@ -73,14 +74,13 @@ namespace Emby.Server.Implementations.HttpServer /// Type of the content. /// The logger. public StreamWriter(byte[] source, string contentType, ILogger logger) - : this(new MemoryStream(source), contentType, logger) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException("contentType"); } - _bytes = source; + SourceBytes = source; Logger = logger; Headers["Content-Type"] = contentType; @@ -92,9 +92,11 @@ namespace Emby.Server.Implementations.HttpServer { try { - if (_bytes != null) + var bytes = SourceBytes; + + if (bytes != null) { - await responseStream.WriteAsync(_bytes, 0, _bytes.Length); + await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } else { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index df491ce85..c1a7347b5 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -142,6 +142,7 @@ + diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs index c4a25a926..6bb3b6b80 100644 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs @@ -1,6 +1,8 @@ using MediaBrowser.Common.Net; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback @@ -8,7 +10,7 @@ namespace MediaBrowser.Api.Playback /// /// Class StaticRemoteStreamWriter /// - public class StaticRemoteStreamWriter : IStreamWriter, IHasHeaders + public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders { /// /// The _input stream @@ -34,15 +36,11 @@ namespace MediaBrowser.Api.Playback get { return _options; } } - /// - /// Writes to. - /// - /// The response stream. - public void WriteTo(Stream responseStream) + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { using (_response) { - _response.Content.CopyTo(responseStream, 819200); + await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Api/TestService.cs b/MediaBrowser.Api/TestService.cs new file mode 100644 index 000000000..5340b816c --- /dev/null +++ b/MediaBrowser.Api/TestService.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace MediaBrowser.Api +{ + [Route("/Test/String", "GET")] + public class GetString + { + } + + [Route("/Test/OptimizedString", "GET")] + public class GetOptimizedString + { + } + + [Route("/Test/Bytes", "GET")] + public class GetBytes + { + } + + [Route("/Test/OptimizedBytes", "GET")] + public class GetOptimizedBytes + { + } + + [Route("/Test/Stream", "GET")] + public class GetStream + { + } + + [Route("/Test/OptimizedStream", "GET")] + public class GetOptimizedStream + { + } + + [Route("/Test/BytesWithContentType", "GET")] + public class GetBytesWithContentType + { + } + + public class TestService : BaseApiService + { + public object Get(GetString request) + { + return "Welcome to Emby!"; + } + public object Get(GetOptimizedString request) + { + return ToOptimizedResult("Welcome to Emby!"); + } + public object Get(GetBytes request) + { + return Encoding.UTF8.GetBytes("Welcome to Emby!"); + } + public object Get(GetOptimizedBytes request) + { + return ToOptimizedResult(Encoding.UTF8.GetBytes("Welcome to Emby!")); + } + public object Get(GetBytesWithContentType request) + { + return ApiEntryPoint.Instance.ResultFactory.GetResult(Encoding.UTF8.GetBytes("Welcome to Emby!"), "text/html"); + } + public object Get(GetStream request) + { + return new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!")); + } + public object Get(GetOptimizedStream request) + { + return ToOptimizedResult(new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!"))); + } + } +} diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs index 36ffeb284..fcb137c6b 100644 --- a/MediaBrowser.Model/Services/IHttpResult.cs +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -19,11 +19,6 @@ namespace MediaBrowser.Model.Services /// HttpStatusCode StatusCode { get; set; } - /// - /// The HTTP Status Description - /// - string StatusDescription { get; set; } - /// /// The HTTP Response ContentType /// diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 5a4d24007..455a69d37 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -136,34 +136,18 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// The Response DTO - /// - object Dto { get; set; } - /// /// Write once to the Response Stream then close it. /// /// void Write(string text); - /// - /// Buffer the Response OutputStream so it can be written in 1 batch - /// - bool UseBufferedStream { get; set; } - /// /// Signal that this response has been handled and no more processing should be done. /// When used in a request or response filter, no more filters or processing is done on this request. /// void Close(); - /// - /// Calls Response.End() on ASP.NET HttpResponse otherwise is an alias for Close(). - /// Useful when you want to prevent ASP.NET to provide it's own custom error page. - /// - void End(); - /// /// Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET /// diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs index 22fdc3e50..58ba29801 100644 --- a/ServiceStack/Host/ContentTypes.cs +++ b/ServiceStack/Host/ContentTypes.cs @@ -13,37 +13,31 @@ namespace ServiceStack.Host public void SerializeToStream(IRequest req, object response, Stream responseStream) { var contentType = req.ResponseContentType; - var serializer = GetResponseSerializer(contentType); - if (serializer == null) - throw new NotSupportedException("ContentType not supported: " + contentType); + var serializer = GetStreamSerializer(contentType); - var httpRes = new HttpResponseStreamWrapper(responseStream, req) - { - Dto = req.Response.Dto - }; - serializer(req, response, httpRes); + serializer(response, responseStream); } - public Action GetResponseSerializer(string contentType) + public Action GetResponseSerializer(string contentType) { var serializer = GetStreamSerializer(contentType); if (serializer == null) return null; - return (httpReq, dto, httpRes) => serializer(httpReq, dto, httpRes.OutputStream); + return (dto, httpRes) => serializer(dto, httpRes.OutputStream); } - public Action GetStreamSerializer(string contentType) + public Action GetStreamSerializer(string contentType) { switch (GetRealContentType(contentType)) { case "application/xml": case "text/xml": case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml - return (r, o, s) => ServiceStackHost.Instance.SerializeToXml(o, s); + return (o, s) => ServiceStackHost.Instance.SerializeToXml(o, s); case "application/json": case "text/json": - return (r, o, s) => ServiceStackHost.Instance.SerializeToJson(o, s); + return (o, s) => ServiceStackHost.Instance.SerializeToJson(o, s); } return null; diff --git a/ServiceStack/Host/HttpResponseStreamWrapper.cs b/ServiceStack/Host/HttpResponseStreamWrapper.cs deleted file mode 100644 index 33038da72..000000000 --- a/ServiceStack/Host/HttpResponseStreamWrapper.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; - -namespace ServiceStack.Host -{ - public class HttpResponseStreamWrapper : IHttpResponse - { - private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); - - public HttpResponseStreamWrapper(Stream stream, IRequest request) - { - this.OutputStream = stream; - this.Request = request; - this.Headers = new Dictionary(); - this.Items = new Dictionary(); - } - - public Dictionary Headers { get; set; } - - public object OriginalResponse - { - get { return null; } - } - - public IRequest Request { get; private set; } - - public int StatusCode { set; get; } - public string StatusDescription { set; get; } - public string ContentType { get; set; } - - public void AddHeader(string name, string value) - { - this.Headers[name] = value; - } - - public string GetHeader(string name) - { - return this.Headers[name]; - } - - public void Redirect(string url) - { - this.Headers["Location"] = url; - } - - public Stream OutputStream { get; private set; } - - public object Dto { get; set; } - - public void Write(string text) - { - var bytes = UTF8EncodingWithoutBom.GetBytes(text); - OutputStream.Write(bytes, 0, bytes.Length); - } - - public bool UseBufferedStream { get; set; } - - public void Close() - { - if (IsClosed) return; - - OutputStream.Dispose(); - IsClosed = true; - } - - public void End() - { - Close(); - } - - public void Flush() - { - OutputStream.Flush(); - } - - public bool IsClosed { get; private set; } - - public void SetContentLength(long contentLength) {} - - public bool KeepAlive { get; set; } - - public Dictionary Items { get; private set; } - - public void SetCookie(Cookie cookie) - { - } - - public void ClearCookies() - { - } - } -} \ No newline at end of file diff --git a/ServiceStack/Host/ServiceController.cs b/ServiceStack/Host/ServiceController.cs index 703f06365..7eb1253b3 100644 --- a/ServiceStack/Host/ServiceController.cs +++ b/ServiceStack/Host/ServiceController.cs @@ -210,9 +210,6 @@ namespace ServiceStack.Host //Executes the service and returns the result var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false); - if (req.Response.Dto == null) - req.Response.Dto = response; - return response; } } diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 1195f63ca..88b82bdf6 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -33,8 +33,11 @@ namespace ServiceStack var stream = result as Stream; if (stream != null) { - WriteTo(stream, response.OutputStream); - return true; + using (stream) + { + await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + return true; + } } var bytes = result as byte[]; @@ -43,35 +46,13 @@ namespace ServiceStack response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - response.OutputStream.Write(bytes, 0, bytes.Length); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return true; } return false; } - public static long WriteTo(Stream inStream, Stream outStream) - { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) - { - memoryStream.WriteTo(outStream); - return memoryStream.Position; - } - - var data = new byte[4096]; - long total = 0; - int bytesRead; - - while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0) - { - outStream.Write(data, 0, bytesRead); - total += bytesRead; - } - - return total; - } - /// /// End a ServiceStack Request with no content /// @@ -85,7 +66,7 @@ namespace ServiceStack httpRes.SetContentLength(0); } - public static Task WriteToResponse(this IResponse httpRes, MediaBrowser.Model.Services.IRequest httpReq, object result) + public static Task WriteToResponse(this IResponse httpRes, IRequest httpReq, object result) { if (result == null) { @@ -98,19 +79,10 @@ namespace ServiceStack { httpResult.RequestContext = httpReq; httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType; - var httpResSerializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); - return httpRes.WriteToResponse(httpResult, httpResSerializer, httpReq); + return httpRes.WriteToResponseInternal(httpResult, httpReq); } - var serializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); - return httpRes.WriteToResponse(result, serializer, httpReq); - } - - private static object GetDto(object response) - { - if (response == null) return null; - var httpResult = response as IHttpResult; - return httpResult != null ? httpResult.Response : response; + return httpRes.WriteToResponseInternal(result, httpReq); } /// @@ -119,17 +91,11 @@ namespace ServiceStack /// /// The response. /// Whether or not it was implicity handled by ServiceStack's built-in handlers. - /// The default action. /// The serialization context. /// - public static async Task WriteToResponse(this IResponse response, object result, Action defaultAction, MediaBrowser.Model.Services.IRequest request) + private static async Task WriteToResponseInternal(this IResponse response, object result, IRequest request) { var defaultContentType = request.ResponseContentType; - if (result == null) - { - response.EndRequestWithNoContent(); - return; - } var httpResult = result as IHttpResult; if (httpResult != null) @@ -137,10 +103,8 @@ namespace ServiceStack if (httpResult.RequestContext == null) httpResult.RequestContext = request; - response.Dto = response.Dto ?? GetDto(httpResult); - response.StatusCode = httpResult.Status; - response.StatusDescription = httpResult.StatusDescription ?? httpResult.StatusCode.ToString(); + response.StatusDescription = httpResult.StatusCode.ToString(); if (string.IsNullOrEmpty(httpResult.ContentType)) { httpResult.ContentType = defaultContentType; @@ -159,21 +123,12 @@ namespace ServiceStack } } } - else - { - response.Dto = result; - } - /* Mono Error: Exception: Method not found: 'System.Web.HttpResponse.get_Headers' */ var responseOptions = result as IHasHeaders; if (responseOptions != null) { - //Reserving options with keys in the format 'xx.xxx' (No Http headers contain a '.' so its a safe restriction) - const string reservedOptions = "."; - foreach (var responseHeaders in responseOptions.Headers) { - if (responseHeaders.Key.Contains(reservedOptions)) continue; if (responseHeaders.Key == "Content-Length") { response.SetContentLength(long.Parse(responseHeaders.Value)); @@ -196,42 +151,25 @@ namespace ServiceStack response.ContentType += "; charset=utf-8"; } - var disposableResult = result as IDisposable; var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); if (writeToOutputStreamResult) { response.Flush(); //required for Compression - if (disposableResult != null) disposableResult.Dispose(); return; } - if (httpResult != null) - result = httpResult.Response; - var responseText = result as string; if (responseText != null) { if (response.ContentType == null || response.ContentType == "text/html") response.ContentType = defaultContentType; - response.Write(responseText); + response.Write(responseText); return; } - if (defaultAction == null) - { - throw new ArgumentNullException("defaultAction", String.Format( - "As result '{0}' is not a supported responseType, a defaultAction must be supplied", - (result != null ? result.GetType().GetOperationName() : ""))); - } - - - if (result != null) - defaultAction(request, result, response); - - if (disposableResult != null) - disposableResult.Dispose(); + var serializer = ContentTypes.Instance.GetResponseSerializer(defaultContentType); + serializer(result, response); } - } } diff --git a/ServiceStack/HttpResult.cs b/ServiceStack/HttpResult.cs index 23a5cdffb..e25002b3e 100644 --- a/ServiceStack/HttpResult.cs +++ b/ServiceStack/HttpResult.cs @@ -13,31 +13,7 @@ namespace ServiceStack public class HttpResult : IHttpResult, IAsyncStreamWriter { - public HttpResult() - : this((object)null, null) - { - } - - public HttpResult(object response) - : this(response, null) - { - } - - public HttpResult(object response, string contentType) - : this(response, contentType, HttpStatusCode.OK) - { - } - - public HttpResult(HttpStatusCode statusCode, string statusDescription) - : this() - { - StatusCode = statusCode; - StatusDescription = statusDescription; - } - - public HttpResult(object response, HttpStatusCode statusCode) - : this(response, null, statusCode) - { } + public object Response { get; set; } public HttpResult(object response, string contentType, HttpStatusCode statusCode) { @@ -49,102 +25,12 @@ namespace ServiceStack this.StatusCode = statusCode; } - public HttpResult(Stream responseStream, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseStream = responseStream; - } - - public HttpResult(string responseText, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseText = responseText; - } - - public HttpResult(byte[] responseBytes, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseStream = new MemoryStream(responseBytes); - } - - public string ResponseText { get; private set; } - - public Stream ResponseStream { get; private set; } - public string ContentType { get; set; } public IDictionary Headers { get; private set; } public List Cookies { get; private set; } - public string ETag { get; set; } - - public TimeSpan? Age { get; set; } - - public TimeSpan? MaxAge { get; set; } - - public DateTime? Expires { get; set; } - - public DateTime? LastModified { get; set; } - - public Func ResultScope { get; set; } - - public string Location - { - set - { - if (StatusCode == HttpStatusCode.OK) - StatusCode = HttpStatusCode.Redirect; - - this.Headers["Location"] = value; - } - } - - public void SetPermanentCookie(string name, string value) - { - SetCookie(name, value, DateTime.UtcNow.AddYears(20), null); - } - - public void SetPermanentCookie(string name, string value, string path) - { - SetCookie(name, value, DateTime.UtcNow.AddYears(20), path); - } - - public void SetSessionCookie(string name, string value) - { - SetSessionCookie(name, value, null); - } - - public void SetSessionCookie(string name, string value, string path) - { - path = path ?? "/"; - this.Headers["Set-Cookie"] = string.Format("{0}={1};path=" + path, name, value); - } - - public void SetCookie(string name, string value, TimeSpan expiresIn, string path) - { - var expiresAt = DateTime.UtcNow.Add(expiresIn); - SetCookie(name, value, expiresAt, path); - } - - public void SetCookie(string name, string value, DateTime expiresAt, string path, bool secure = false, bool httpOnly = false) - { - path = path ?? "/"; - var cookie = string.Format("{0}={1};expires={2};path={3}", name, value, expiresAt.ToString("R"), path); - if (secure) - cookie += ";Secure"; - if (httpOnly) - cookie += ";HttpOnly"; - - this.Headers["Set-Cookie"] = cookie; - } - - public void DeleteCookie(string name) - { - var cookie = string.Format("{0}=;expires={1};path=/", name, DateTime.UtcNow.AddDays(-1).ToString("R")); - this.Headers["Set-Cookie"] = cookie; - } - public int Status { get; set; } public HttpStatusCode StatusCode @@ -153,75 +39,12 @@ namespace ServiceStack set { Status = (int)value; } } - public string StatusDescription { get; set; } - - public object Response { get; set; } - - public MediaBrowser.Model.Services.IRequest RequestContext { get; set; } - - public string View { get; set; } - - public string Template { get; set; } - - public int PaddingLength { get; set; } + public IRequest RequestContext { get; set; } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) - { - try - { - await WriteToInternalAsync(responseStream, cancellationToken).ConfigureAwait(false); - responseStream.Flush(); - } - finally - { - DisposeStream(); - } - } - - public static Task WriteTo(Stream inStream, Stream outStream, CancellationToken cancellationToken) - { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) - { - memoryStream.WriteTo(outStream); - return Task.FromResult(true); - } - - return inStream.CopyToAsync(outStream, 81920, cancellationToken); - } - - public async Task WriteToInternalAsync(Stream responseStream, CancellationToken cancellationToken) { var response = RequestContext != null ? RequestContext.Response : null; - if (this.ResponseStream != null) - { - if (response != null) - { - var ms = ResponseStream as MemoryStream; - if (ms != null) - { - response.SetContentLength(ms.Length); - - await ms.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); - return; - } - } - - await WriteTo(this.ResponseStream, responseStream, cancellationToken).ConfigureAwait(false); - return; - } - - if (this.ResponseText != null) - { - var bytes = Encoding.UTF8.GetBytes(this.ResponseText); - if (response != null) - response.SetContentLength(bytes.Length); - - await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - return; - } - var bytesResponse = this.Response as byte[]; if (bytesResponse != null) { @@ -234,17 +57,5 @@ namespace ServiceStack ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream); } - - private void DisposeStream() - { - try - { - if (ResponseStream != null) - { - this.ResponseStream.Dispose(); - } - } - catch { /*ignore*/ } - } } } diff --git a/ServiceStack/ServiceStack.csproj b/ServiceStack/ServiceStack.csproj index 3402339a6..5413d4e55 100644 --- a/ServiceStack/ServiceStack.csproj +++ b/ServiceStack/ServiceStack.csproj @@ -73,7 +73,6 @@ - diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index b50660ad0..52385e2ba 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -119,7 +119,6 @@ namespace SocketHttpListener.Net if (listener == null) return false; - context.Listener = listener; context.Connection.Prefix = prefix; return true; } @@ -129,7 +128,7 @@ namespace SocketHttpListener.Net if (context == null || context.Request == null) return; - context.Listener.UnregisterContext(context); + listener.UnregisterContext(context); } HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index d31da4132..db34c4218 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; @@ -210,12 +209,7 @@ namespace SocketHttpListener.Net // TODO: can we get this stream before reading the input? if (o_stream == null) { - HttpListener listener = context.Listener; - - if (listener == null) - return new ResponseStream(stream, context.Response, true, _memoryStreamFactory, _textEncoding); - - o_stream = new ResponseStream(stream, context.Response, listener.IgnoreWriteExceptions, _memoryStreamFactory, _textEncoding); + o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); } return o_stream; } @@ -257,7 +251,7 @@ namespace SocketHttpListener.Net Close(true); return; } - HttpListener listener = context.Listener; + HttpListener listener = epl.Listener; if (last_listener != listener) { RemoveConnection(); diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs index 83660100a..2b0f75d01 100644 --- a/SocketHttpListener.Portable/Net/HttpListener.cs +++ b/SocketHttpListener.Portable/Net/HttpListener.cs @@ -28,7 +28,6 @@ namespace SocketHttpListener.Net HttpListenerPrefixCollection prefixes; AuthenticationSchemeSelector auth_selector; string realm; - bool ignore_write_exceptions; bool unsafe_ntlm_auth; bool listening; bool disposed; @@ -92,16 +91,6 @@ namespace SocketHttpListener.Net } } - public bool IgnoreWriteExceptions - { - get { return ignore_write_exceptions; } - set - { - CheckDisposed(); - ignore_write_exceptions = value; - } - } - public bool IsListening { get { return listening; } diff --git a/SocketHttpListener.Portable/Net/HttpListenerContext.cs b/SocketHttpListener.Portable/Net/HttpListenerContext.cs index 84c6a8c19..182fd2d2a 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerContext.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerContext.cs @@ -18,7 +18,6 @@ namespace SocketHttpListener.Net HttpConnection cnc; string error; int err_status = 400; - internal HttpListener Listener; private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index 6ecbf9742..07788ea41 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -17,17 +17,15 @@ namespace SocketHttpListener.Net class ResponseStream : Stream { HttpListenerResponse response; - bool ignore_errors; bool disposed; bool trailer_sent; Stream stream; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; - internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) { this.response = response; - this.ignore_errors = ignore_errors; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; this.stream = stream; @@ -130,18 +128,7 @@ namespace SocketHttpListener.Net internal void InternalWrite(byte[] buffer, int offset, int count) { - if (ignore_errors) - { - try - { - stream.Write(buffer, offset, count); - } - catch { } - } - else - { - stream.Write(buffer, offset, count); - } + stream.Write(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) @@ -214,23 +201,13 @@ namespace SocketHttpListener.Net InternalWrite(bytes, 0, bytes.Length); } - try - { - if (count > 0) - { - await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - } - - if (response.SendChunked) - stream.Write(crlf, 0, 2); - } - catch + if (count > 0) { - if (!ignore_errors) - { - throw; - } + await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } + + if (response.SendChunked) + stream.Write(crlf, 0, 2); } //public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, -- cgit v1.2.3 From a855864207fe3ed0ac9b4d648617bb1cb39df3f3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 02:14:04 -0500 Subject: limit access to response stream --- .../HttpServer/HttpListenerHost.cs | 22 +++++++++++++++------- .../SocketSharp/WebSocketSharpResponse.cs | 21 ++++----------------- MediaBrowser.Model/Services/IRequest.cs | 13 ------------- ServiceStack/Host/ContentTypes.cs | 10 +--------- ServiceStack/HttpResponseExtensionsInternal.cs | 22 ++++++++++++---------- 5 files changed, 32 insertions(+), 56 deletions(-) (limited to 'ServiceStack') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 49c664eec..41b7a4622 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; @@ -248,9 +249,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - httpRes.Write(ex.Message); - - httpRes.Close(); + Write(httpRes, ex.Message); } catch { @@ -404,7 +403,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - httpRes.Write("Invalid host"); + Write(httpRes, "Invalid host"); return; } @@ -458,7 +457,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); return; @@ -475,7 +474,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "EmbyPlease update your Emby bookmark to " + newUrl + ""); return; @@ -513,7 +512,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/html"; - httpRes.Write(GlobalResponse); + Write(httpRes, GlobalResponse); return; } @@ -547,6 +546,15 @@ namespace Emby.Server.Implementations.HttpServer } } + private void Write(IResponse response, string text) + { + var bOutput = Encoding.UTF8.GetBytes(text); + response.SetContentLength(bOutput.Length); + + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + } + public static void RedirectToUrl(IResponse httpRes, string url) { httpRes.StatusCode = 302; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 9de86e9cc..a8b115056 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -77,16 +77,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return _response.OutputStream; } } - public void Write(string text) - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - _response.ContentLength64 = bOutput.Length; - - var outputStream = _response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - public void Close() { if (!this.IsClosed) @@ -108,8 +98,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp { try { - response.OutputStream.Flush(); - response.OutputStream.Dispose(); + var outputStream = response.OutputStream; + + outputStream.Flush(); + outputStream.Dispose(); response.Close(); } catch (Exception ex) @@ -118,11 +110,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } } - public void Flush() - { - _response.OutputStream.Flush(); - } - public bool IsClosed { get; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 455a69d37..e9a9f1c5b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -136,23 +136,12 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// Write once to the Response Stream then close it. - /// - /// - void Write(string text); - /// /// Signal that this response has been handled and no more processing should be done. /// When used in a request or response filter, no more filters or processing is done on this request. /// void Close(); - /// - /// Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET - /// - void Flush(); - /// /// Gets a value indicating whether this instance is closed. /// @@ -160,8 +149,6 @@ namespace MediaBrowser.Model.Services void SetContentLength(long contentLength); - bool KeepAlive { get; set; } - //Add Metadata to Response Dictionary Items { get; } } diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs index 58ba29801..8840e7c8b 100644 --- a/ServiceStack/Host/ContentTypes.cs +++ b/ServiceStack/Host/ContentTypes.cs @@ -18,15 +18,7 @@ namespace ServiceStack.Host serializer(response, responseStream); } - public Action GetResponseSerializer(string contentType) - { - var serializer = GetStreamSerializer(contentType); - if (serializer == null) return null; - - return (dto, httpRes) => serializer(dto, httpRes.OutputStream); - } - - public Action GetStreamSerializer(string contentType) + private Action GetStreamSerializer(string contentType) { switch (GetRealContentType(contentType)) { diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 88b82bdf6..44b790f5f 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -6,6 +6,7 @@ using System.IO; using System.Net; using System.Threading.Tasks; using System.Collections.Generic; +using System.Text; using System.Threading; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -14,19 +15,19 @@ namespace ServiceStack { public static class HttpResponseExtensionsInternal { - public static async Task WriteToOutputStream(IResponse response, object result) + public static async Task WriteToOutputStream(IResponse response, Stream outputStream, object result) { var asyncStreamWriter = result as IAsyncStreamWriter; if (asyncStreamWriter != null) { - await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + await asyncStreamWriter.WriteToAsync(outputStream, CancellationToken.None).ConfigureAwait(false); return true; } var streamWriter = result as IStreamWriter; if (streamWriter != null) { - streamWriter.WriteTo(response.OutputStream); + streamWriter.WriteTo(outputStream); return true; } @@ -35,7 +36,7 @@ namespace ServiceStack { using (stream) { - await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + await stream.CopyToAsync(outputStream).ConfigureAwait(false); return true; } } @@ -46,7 +47,7 @@ namespace ServiceStack response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return true; } @@ -151,10 +152,11 @@ namespace ServiceStack response.ContentType += "; charset=utf-8"; } - var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); + var outputStream = response.OutputStream; + + var writeToOutputStreamResult = await WriteToOutputStream(response, outputStream, result).ConfigureAwait(false); if (writeToOutputStreamResult) { - response.Flush(); //required for Compression return; } @@ -164,12 +166,12 @@ namespace ServiceStack if (response.ContentType == null || response.ContentType == "text/html") response.ContentType = defaultContentType; - response.Write(responseText); + var bytes = Encoding.UTF8.GetBytes(responseText); + await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } - var serializer = ContentTypes.Instance.GetResponseSerializer(defaultContentType); - serializer(result, response); + ContentTypes.Instance.SerializeToStream(request, result, outputStream); } } } -- cgit v1.2.3 From 6865bb53530abbf703b590dd39cfa27274b03107 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 02:45:06 -0500 Subject: update responses --- ServiceStack/Host/ContentTypes.cs | 2 +- ServiceStack/HttpResponseExtensionsInternal.cs | 35 ++++++++++++++++++-------- ServiceStack/HttpResult.cs | 2 +- 3 files changed, 27 insertions(+), 12 deletions(-) (limited to 'ServiceStack') diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs index 8840e7c8b..f7734a36b 100644 --- a/ServiceStack/Host/ContentTypes.cs +++ b/ServiceStack/Host/ContentTypes.cs @@ -18,7 +18,7 @@ namespace ServiceStack.Host serializer(response, responseStream); } - private Action GetStreamSerializer(string contentType) + public static Action GetStreamSerializer(string contentType) { switch (GetRealContentType(contentType)) { diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 44b790f5f..318d62429 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -15,19 +15,19 @@ namespace ServiceStack { public static class HttpResponseExtensionsInternal { - public static async Task WriteToOutputStream(IResponse response, Stream outputStream, object result) + public static async Task WriteToOutputStream(IResponse response, object result) { var asyncStreamWriter = result as IAsyncStreamWriter; if (asyncStreamWriter != null) { - await asyncStreamWriter.WriteToAsync(outputStream, CancellationToken.None).ConfigureAwait(false); + await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); return true; } var streamWriter = result as IStreamWriter; if (streamWriter != null) { - streamWriter.WriteTo(outputStream); + streamWriter.WriteTo(response.OutputStream); return true; } @@ -36,7 +36,7 @@ namespace ServiceStack { using (stream) { - await stream.CopyToAsync(outputStream).ConfigureAwait(false); + await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); return true; } } @@ -47,7 +47,7 @@ namespace ServiceStack response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return true; } @@ -152,9 +152,7 @@ namespace ServiceStack response.ContentType += "; charset=utf-8"; } - var outputStream = response.OutputStream; - - var writeToOutputStreamResult = await WriteToOutputStream(response, outputStream, result).ConfigureAwait(false); + var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); if (writeToOutputStreamResult) { return; @@ -167,11 +165,28 @@ namespace ServiceStack response.ContentType = defaultContentType; var bytes = Encoding.UTF8.GetBytes(responseText); - await outputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } - ContentTypes.Instance.SerializeToStream(request, result, outputStream); + await WriteObject(request, result, response).ConfigureAwait(false); + } + + public static async Task WriteObject(IRequest request, object result, IResponse response) + { + var contentType = request.ResponseContentType; + var serializer = ContentTypes.GetStreamSerializer(contentType); + + using (var ms = new MemoryStream()) + { + serializer(result, ms); + + ms.Position = 0; + response.SetContentLength(ms.Length); + await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + } + + //serializer(result, outputStream); } } } diff --git a/ServiceStack/HttpResult.cs b/ServiceStack/HttpResult.cs index e25002b3e..3f86ffdf7 100644 --- a/ServiceStack/HttpResult.cs +++ b/ServiceStack/HttpResult.cs @@ -55,7 +55,7 @@ namespace ServiceStack return; } - ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream); + await HttpResponseExtensionsInternal.WriteObject(this.RequestContext, this.Response, response).ConfigureAwait(false); } } } -- cgit v1.2.3 From 102bbe2beb76fa76b21f4ed3f7c584a58d787204 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 17:46:38 -0500 Subject: fix mono project --- .../MediaBrowser.Server.Mono.csproj | 44 ++++++++++++++++++++++ ServiceStack/Host/RestHandler.cs | 36 +++--------------- ServiceStack/ServiceStackHost.Runtime.cs | 21 +---------- SocketHttpListener.Portable/Net/HttpConnection.cs | 6 +-- .../Net/HttpListenerRequest.cs | 13 +++++-- .../Net/HttpListenerResponse.cs | 4 +- 6 files changed, 66 insertions(+), 58 deletions(-) (limited to 'ServiceStack') diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 090e8ea9a..ca830b2f4 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -115,6 +115,34 @@ + + {88ae38df-19d7-406f-a6a9-09527719a21e} + BDInfo + + + {713f42b5-878e-499d-a878-e4c652b1d5e8} + DvdLib + + + {805844ab-e92f-45e6-9d99-4f6d48d129a5} + Emby.Dlna + + + {6cfee013-6e7c-432b-ac37-cabf0880c69a} + Emby.Drawing.ImageMagick + + + {c97a239e-a96c-4d64-a844-ccf8cc30aecb} + Emby.Drawing.Net + + + {08fff49b-f175-4807-a2b5-73b0ebd9f716} + Emby.Drawing + + + {89ab4548-770d-41fd-a891-8daff44f452c} + Emby.Photos + {e383961b-9356-4d5d-8233-9a1079d03055} Emby.Server.Implementations @@ -163,6 +191,22 @@ {23499896-B135-4527-8574-C26E926EA99E} MediaBrowser.XbmcMetadata + + {4a4402d4-e910-443b-b8fc-2c18286a2ca0} + OpenSubtitlesHandler + + + {21002819-c39a-4d3e-be83-2a276a77fb1f} + RSSDP + + + {680a1709-25eb-4d52-a87f-ee03ffd94baa} + ServiceStack + + + {4f26d5d8-a7b0-42b3-ba42-7cb7d245934e} + SocketHttpListener.Portable + diff --git a/ServiceStack/Host/RestHandler.cs b/ServiceStack/Host/RestHandler.cs index 5c360d150..1eae6be38 100644 --- a/ServiceStack/Host/RestHandler.cs +++ b/ServiceStack/Host/RestHandler.cs @@ -23,32 +23,13 @@ namespace ServiceStack.Host var taskResult = ServiceStackHost.Instance.GetTaskResult(taskResponse, RequestName); - var taskResults = taskResult as Task[]; - - if (taskResults == null) - { - var subTask = taskResult as Task; - if (subTask != null) - taskResult = ServiceStackHost.Instance.GetTaskResult(subTask, RequestName); - - return taskResult; - } - - if (taskResults.Length == 0) + var subTask = taskResult as Task; + if (subTask != null) { - return new object[] { }; + taskResult = ServiceStackHost.Instance.GetTaskResult(subTask, RequestName); } - var firstResponse = ServiceStackHost.Instance.GetTaskResult(taskResults[0], RequestName); - var batchedResponses = firstResponse != null - ? (object[])Array.CreateInstance(firstResponse.GetType(), taskResults.Length) - : new object[taskResults.Length]; - batchedResponses[0] = firstResponse; - for (var i = 1; i < taskResults.Length; i++) - { - batchedResponses[i] = ServiceStackHost.Instance.GetTaskResult(taskResults[i], RequestName); - } - return batchedResponses; + return taskResult; } protected static object CreateContentTypeRequest(IRequest httpReq, Type requestType, string contentType) @@ -141,18 +122,13 @@ namespace ServiceStack.Host var request = httpReq.Dto = CreateRequest(httpReq, restPath); - if (appHost.ApplyRequestFilters(httpReq, httpRes, request)) - return; + appHost.ApplyRequestFilters(httpReq, httpRes, request); var rawResponse = await ServiceStackHost.Instance.ServiceController.Execute(request, httpReq).ConfigureAwait(false); - if (httpRes.IsClosed) - return; - var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false); - if (appHost.ApplyResponseFilters(httpReq, httpRes, response)) - return; + appHost.ApplyResponseFilters(httpReq, httpRes, response); await httpRes.WriteToResponse(httpReq, response).ConfigureAwait(false); } diff --git a/ServiceStack/ServiceStackHost.Runtime.cs b/ServiceStack/ServiceStackHost.Runtime.cs index 1a1656a0e..aaa50633b 100644 --- a/ServiceStack/ServiceStackHost.Runtime.cs +++ b/ServiceStack/ServiceStackHost.Runtime.cs @@ -14,10 +14,8 @@ namespace ServiceStack /// and no more processing should be done. /// /// - public virtual bool ApplyRequestFilters(IRequest req, IResponse res, object requestDto) + public virtual void ApplyRequestFilters(IRequest req, IResponse res, object requestDto) { - if (res.IsClosed) return res.IsClosed; - //Exec all RequestFilter attributes with Priority < 0 var attributes = FilterAttributeCache.GetRequestFilterAttributes(requestDto.GetType()); var i = 0; @@ -25,16 +23,12 @@ namespace ServiceStack { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); - if (res.IsClosed) return res.IsClosed; } - if (res.IsClosed) return res.IsClosed; - //Exec global filters foreach (var requestFilter in GlobalRequestFilters) { requestFilter(req, res, requestDto); - if (res.IsClosed) return res.IsClosed; } //Exec remaining RequestFilter attributes with Priority >= 0 @@ -42,10 +36,7 @@ namespace ServiceStack { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); - if (res.IsClosed) return res.IsClosed; } - - return res.IsClosed; } /// @@ -53,21 +44,13 @@ namespace ServiceStack /// and no more processing should be done. /// /// - public virtual bool ApplyResponseFilters(IRequest req, IResponse res, object response) + public virtual void ApplyResponseFilters(IRequest req, IResponse res, object response) { - if (response != null) - { - if (res.IsClosed) return res.IsClosed; - } - //Exec global filters foreach (var responseFilter in GlobalResponseFilters) { responseFilter(req, res, response); - if (res.IsClosed) return res.IsClosed; } - - return res.IsClosed; } } diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index 8e472117e..67dd5c958 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -204,12 +204,12 @@ namespace SocketHttpListener.Net return i_stream; } - public Stream GetResponseStream() + public Stream GetResponseStream(HttpListenerRequest request) { // TODO: can we get this stream before reading the input? if (o_stream == null) { - if (context.Response.SendChunked) + if (context.Response.SendChunked || request == null || request.HasExpect100Continue) { o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); } @@ -490,7 +490,7 @@ namespace SocketHttpListener.Net { if (!context.Request.IsWebSocketRequest || force_close) { - Stream st = GetResponseStream(); + Stream st = GetResponseStream(context.Request); if (st != null) st.Dispose(); diff --git a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs index 5631fc0a1..767f1c542 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs @@ -179,16 +179,21 @@ namespace SocketHttpListener.Net } } - if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) + if (HasExpect100Continue) { - var output = context.Connection.GetResponseStream(); - + var output = (ResponseStream)context.Connection.GetResponseStream(this); + var _100continue = _textEncoding.GetASCIIEncoding().GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); - //output.InternalWrite(_100continue, 0, _100continue.Length); + output.InternalWrite(_100continue, 0, _100continue.Length); } } + public bool HasExpect100Continue + { + get { return String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0; } + } + static bool MaybeUri(string s) { int p = s.IndexOf(':'); diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index 93358cae4..8c610d725 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -149,7 +149,7 @@ namespace SocketHttpListener.Net get { if (output_stream == null) - output_stream = context.Connection.GetResponseStream(); + output_stream = context.Connection.GetResponseStream(context.Request); return output_stream; } } @@ -489,7 +489,7 @@ namespace SocketHttpListener.Net int preamble = encoding.GetPreamble().Length; if (output_stream == null) - output_stream = context.Connection.GetResponseStream(); + output_stream = context.Connection.GetResponseStream(context.Request); /* Assumes that the ms was at position 0 */ ms.Position = preamble; -- cgit v1.2.3 From 3e06bda46b2569c1df47d9ab0c7a763dcf3b1451 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 12 Nov 2016 20:53:51 -0500 Subject: update components --- ServiceStack/HttpResponseExtensionsInternal.cs | 4 +--- SocketHttpListener.Portable/Net/HttpConnection.cs | 18 ++++++----------- .../Net/HttpListenerResponse.cs | 23 +--------------------- 3 files changed, 8 insertions(+), 37 deletions(-) (limited to 'ServiceStack') diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 318d62429..f78647721 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -161,10 +161,8 @@ namespace ServiceStack var responseText = result as string; if (responseText != null) { - if (response.ContentType == null || response.ContentType == "text/html") - response.ContentType = defaultContentType; - var bytes = Encoding.UTF8.GetBytes(responseText); + response.SetContentLength(bytes.Length); await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index d9fd97ed2..b09d02254 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -209,7 +209,7 @@ namespace SocketHttpListener.Net // TODO: can we get this stream before reading the input? if (o_stream == null) { - if (context.Response.SendChunked || isExpect100Continue) + if (context.Response.SendChunked || isExpect100Continue || context.Response.ContentLength64 <= 0) { o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); } @@ -438,7 +438,9 @@ namespace SocketHttpListener.Net str = String.Format("

{0}

", description); byte[] error = context.Response.ContentEncoding.GetBytes(str); - response.Close(error, false); + response.ContentLength64 = error.Length; + response.OutputStream.Write(error, 0, (int)error.Length); + response.Close(); } catch { @@ -492,7 +494,9 @@ namespace SocketHttpListener.Net { Stream st = GetResponseStream(); if (st != null) + { st.Dispose(); + } o_stream = null; } @@ -514,16 +518,6 @@ namespace SocketHttpListener.Net if (!force_close && context.Request.FlushInput()) { - if (chunked && context.Response.ForceCloseChunked == false) - { - // Don't close. Keep working. - reuses++; - Unbind(); - Init(); - BeginReadRequest(); - return; - } - reuses++; Unbind(); Init(); diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index 93358cae4..fb3bc2bdb 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -30,8 +30,6 @@ namespace SocketHttpListener.Net internal bool HeadersSent; internal object headers_lock = new object(); - bool force_close_chunked; - private readonly ILogger _logger; private readonly ITextEncoding _textEncoding; @@ -50,11 +48,6 @@ namespace SocketHttpListener.Net } } - internal bool ForceCloseChunked - { - get { return force_close_chunked; } - } - public Encoding ContentEncoding { get @@ -327,7 +320,7 @@ namespace SocketHttpListener.Net headers.Add(name, value); } - void Close(bool force) + private void Close(bool force) { if (force) { @@ -345,20 +338,6 @@ namespace SocketHttpListener.Net Close(false); } - public void Close(byte[] responseEntity, bool willBlock) - { - if (disposed) - return; - - if (responseEntity == null) - throw new ArgumentNullException("responseEntity"); - - //TODO: if willBlock -> BeginWrite + Close ? - ContentLength64 = responseEntity.Length; - OutputStream.Write(responseEntity, 0, (int)content_length); - Close(false); - } - public void Redirect(string url) { StatusCode = 302; // Found -- cgit v1.2.3 From 56b24da15165ef4c4b7107b673bab5b191d76afe Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 28 Nov 2016 00:38:41 -0500 Subject: update response stream parsing --- .../HttpServer/SocketSharp/WebSocketSharpResponse.cs | 12 ++++++++---- Emby.Server.Implementations/packages.config | 2 +- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 3 ++- ServiceStack/HttpResponseExtensionsInternal.cs | 2 +- SocketHttpListener.Portable/Net/HttpConnection.cs | 4 +++- SocketHttpListener.Portable/Net/HttpListenerResponse.cs | 16 ++++++++++++++++ SocketHttpListener.Portable/Net/ResponseStream.cs | 2 +- 7 files changed, 32 insertions(+), 9 deletions(-) (limited to 'ServiceStack') diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index dc049cbde..36f795411 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -4,6 +4,7 @@ using System.IO; using System.Net; using System.Text; using MediaBrowser.Model.Logging; +using SocketHttpListener.Net; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; @@ -101,12 +102,15 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp var outputStream = response.OutputStream; // This is needed with compression - //if (!string.IsNullOrWhiteSpace(GetHeader("Content-Encoding"))) + if (outputStream is ResponseStream) { - outputStream.Flush(); - } + //if (!string.IsNullOrWhiteSpace(GetHeader("Content-Encoding"))) + { + outputStream.Flush(); + } - outputStream.Dispose(); + outputStream.Dispose(); + } response.Close(); } catch (Exception ex) diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 5d75af7a2..8464b9b37 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,6 +1,6 @@  - + diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 8d1c0a61e..690acb163 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -153,7 +153,8 @@ namespace MediaBrowser.Api.Playback.Hls var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture); - text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); + text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); + //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); return text; } diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index f78647721..feb18081a 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -130,7 +130,7 @@ namespace ServiceStack { foreach (var responseHeaders in responseOptions.Headers) { - if (responseHeaders.Key == "Content-Length") + if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { response.SetContentLength(long.Parse(responseHeaders.Value)); continue; diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index b09d02254..4b54fc013 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -209,7 +209,9 @@ namespace SocketHttpListener.Net // TODO: can we get this stream before reading the input? if (o_stream == null) { - if (context.Response.SendChunked || isExpect100Continue || context.Response.ContentLength64 <= 0) + context.Response.DetermineIfChunked(); + + if (context.Response.SendChunked || isExpect100Continue || context.Request.IsWebSocketRequest) { o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); } diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index 880473c0a..c1182de34 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -362,6 +362,22 @@ namespace SocketHttpListener.Net return false; } + public void DetermineIfChunked() + { + if (chunked) + { + return ; + } + + Version v = context.Request.ProtocolVersion; + if (!cl_set && !chunked && v >= HttpVersion.Version11) + chunked = true; + if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked")) + { + chunked = true; + } + } + internal void SendHeaders(bool closing, MemoryStream ms) { Encoding encoding = content_encoding; diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index 7a6425dea..6067a89ec 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -14,7 +14,7 @@ namespace SocketHttpListener.Net // Update: we send a single packet for the first non-chunked Write // What happens when we set content-length to X and write X-1 bytes then close? // what if we don't set content-length at all? - class ResponseStream : Stream + public class ResponseStream : Stream { HttpListenerResponse response; bool disposed; -- cgit v1.2.3 From 401a6b8f4a84f378f0f6682ee7aecccc6ab30935 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 4 Dec 2016 16:30:38 -0500 Subject: add request logging --- Emby.Common.Implementations/Net/SocketFactory.cs | 26 ++-- Emby.Common.Implementations/Net/UdpSocket.cs | 46 +++--- .../Networking/NetworkManager.cs | 2 +- Emby.Dlna/ConnectionManager/ControlHandler.cs | 4 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 45 ++++-- Emby.Dlna/Emby.Dlna.csproj | 1 - Emby.Dlna/Main/DlnaEntryPoint.cs | 6 +- Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs | 6 +- Emby.Dlna/Server/Headers.cs | 169 --------------------- Emby.Dlna/Service/BaseControlHandler.cs | 17 +-- .../HttpServer/HttpListenerHost.cs | 2 +- ServiceStack/HttpHandlerFactory.cs | 9 +- 12 files changed, 96 insertions(+), 237 deletions(-) delete mode 100644 Emby.Dlna/Server/Headers.cs (limited to 'ServiceStack') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index c65593242..124252097 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; @@ -18,11 +19,6 @@ namespace Emby.Common.Implementations.Net // but that wasn't really the point so kept to YAGNI principal for now, even if the // interfaces are a bit ugly, specific and make assumptions. - /// - /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. - /// - private IPAddress _LocalIP; - private readonly ILogger _logger; public SocketFactory(ILogger logger) @@ -33,7 +29,6 @@ namespace Emby.Common.Implementations.Net } _logger = logger; - _LocalIP = IPAddress.Any; } public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) @@ -66,7 +61,7 @@ namespace Emby.Common.Implementations.Net try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, IPAddress.Any); } catch { @@ -80,9 +75,8 @@ namespace Emby.Common.Implementations.Net /// /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// - /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. - public IUdpSocket CreateSsdpUdpSocket(int localPort) + public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); @@ -91,8 +85,11 @@ namespace Emby.Common.Implementations.Net { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); - return new UdpSocket(retVal, localPort, _LocalIP); + + var localIp = NetworkManager.ToIPAddress(localIpAddress); + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp)); + return new UdpSocket(retVal, localPort, localIp); } catch { @@ -134,10 +131,13 @@ namespace Emby.Common.Implementations.Net //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + + var localIp = IPAddress.Any; + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), localIp)); retVal.MulticastLoopback = true; - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, localIp); } catch { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 367d2242c..b2af9d162 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -20,7 +20,6 @@ namespace Emby.Common.Implementations.Net private Socket _Socket; private int _LocalPort; - #endregion #region Constructors @@ -31,12 +30,19 @@ namespace Emby.Common.Implementations.Net _Socket = socket; _LocalPort = localPort; + LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); } #endregion + public IpAddressInfo LocalIPAddress + { + get; + private set; + } + #region IUdpSocket Members public Task ReceiveAsync() @@ -50,18 +56,18 @@ namespace Emby.Common.Implementations.Net state.TaskCompletionSource = tcs; #if NETSTANDARD1_6 - _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.EndPoint) + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => { if (task.Status != TaskStatus.Faulted) { var receiveState = asyncState as AsyncReceiveState; - receiveState.EndPoint = task.Result.RemoteEndPoint; - ProcessResponse(receiveState, () => task.Result.ReceivedBytes); + receiveState.RemoteEndPoint = task.Result.RemoteEndPoint; + ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress); } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); #endif return tcs.Task; @@ -74,6 +80,8 @@ namespace Emby.Common.Implementations.Net if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); + var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); + #if NETSTANDARD1_6 if (size != buffer.Length) @@ -83,14 +91,14 @@ namespace Emby.Common.Implementations.Net buffer = copy; } - _Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + _Socket.SendTo(buffer, ipEndPoint); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { - _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => + _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => { try { @@ -109,7 +117,7 @@ namespace Emby.Common.Implementations.Net taskSource.TrySetException(ex); } - //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); return taskSource.Task; #endif @@ -133,19 +141,20 @@ namespace Emby.Common.Implementations.Net #region Private Methods - private static void ProcessResponse(AsyncReceiveState state, Func receiveData) + private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress) { try { var bytesRead = receiveData(); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( - new SocketReceiveResult() + new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = localIpAddress } ); } @@ -182,15 +191,16 @@ namespace Emby.Common.Implementations.Net var state = asyncResult.AsyncState as AsyncReceiveState; try { - var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = LocalIPAddress } ); } @@ -211,13 +221,13 @@ namespace Emby.Common.Implementations.Net private class AsyncReceiveState { - public AsyncReceiveState(Socket socket, EndPoint endPoint) + public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) { this.Socket = socket; - this.EndPoint = endPoint; + this.RemoteEndPoint = remoteEndPoint; } - public EndPoint EndPoint; + public EndPoint RemoteEndPoint; public byte[] Buffer = new byte[8192]; public Socket Socket { get; private set; } diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index a4f8f7ced..b9100f9db 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -27,7 +27,7 @@ namespace Emby.Common.Implementations.Networking private List _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public IEnumerable GetLocalIpAddresses() + public List GetLocalIpAddresses() { const int cacheMinutes = 5; diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 0bc44db17..ae983c5e7 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -14,7 +14,7 @@ namespace Emby.Dlna.ConnectionManager { private readonly DeviceProfile _profile; - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) { @@ -26,7 +26,7 @@ namespace Emby.Dlna.ConnectionManager private IEnumerable> HandleGetProtocolInfo() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Source", _profile.ProtocolInfo }, { "Sink", "" } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index d77919e47..98a151f29 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder); } - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { var deviceId = "test"; @@ -118,17 +118,20 @@ namespace Emby.Dlna.ContentDirectory _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, CancellationToken.None); - return new Headers(); + return new Dictionary(StringComparer.OrdinalIgnoreCase); } private IEnumerable> HandleGetSearchCapabilities() { - return new Headers(true) { { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } }; + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } + }; } private IEnumerable> HandleGetSortCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -136,7 +139,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSortExtensionCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -144,14 +147,14 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSystemUpdateID() { - var headers = new Headers(true); + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); headers.Add("Id", _systemUpdateId.ToString(_usCulture)); return headers; } private IEnumerable> HandleGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -159,7 +162,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleXGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -183,12 +186,24 @@ namespace Emby.Dlna.ContentDirectory return builder.ToString(); } - private async Task>> HandleBrowse(Headers sparams, User user, string deviceId) + public string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) + { + string val; + + if (sparams.TryGetValue(key, out val)) + { + return val; + } + + return defaultValue; + } + + private async Task>> HandleBrowse(IDictionary sparams, User user, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); var provided = 0; @@ -294,11 +309,11 @@ namespace Emby.Dlna.ContentDirectory }; } - private async Task>> HandleSearch(Headers sparams, User user, string deviceId) + private async Task>> HandleSearch(IDictionary sparams, User user, string deviceId) { - var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", "")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); // sort example: dc:title, dc:date diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 0441cb3be..4d1aacfec 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -152,7 +152,6 @@ - diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 170b4cee0..858b1ae9e 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -54,6 +54,7 @@ namespace Emby.Dlna.Main private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; + private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _communicationsServer; @@ -69,7 +70,7 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager) { _config = config; _appHost = appHost; @@ -87,6 +88,7 @@ namespace Emby.Dlna.Main _socketFactory = socketFactory; _timerFactory = timerFactory; _environmentInfo = environmentInfo; + _networkManager = networkManager; _logger = logManager.GetLogger("Dlna"); } @@ -156,7 +158,7 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - _communicationsServer = new SsdpCommunicationsServer(_socketFactory) + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger) { IsShared = true }; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 5e232aeac..daf46b106 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -11,7 +11,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { public class ControlHandler : BaseControlHandler { - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) return HandleIsAuthorized(); @@ -23,7 +23,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsAuthorized() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; @@ -31,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsValidated() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; diff --git a/Emby.Dlna/Server/Headers.cs b/Emby.Dlna/Server/Headers.cs deleted file mode 100644 index 47dd8e321..000000000 --- a/Emby.Dlna/Server/Headers.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Emby.Dlna.Server -{ - public class Headers : IDictionary - { - private readonly bool _asIs = false; - private readonly Dictionary _dict = new Dictionary(); - private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase); - - public Headers(bool asIs) - { - _asIs = asIs; - } - - public Headers() - : this(asIs: false) - { - } - - public int Count - { - get - { - return _dict.Count; - } - } - public string HeaderBlock - { - get - { - var hb = new StringBuilder(); - foreach (var h in this) - { - hb.AppendFormat("{0}: {1}\r\n", h.Key, h.Value); - } - return hb.ToString(); - } - } - public bool IsReadOnly - { - get - { - return false; - } - } - public ICollection Keys - { - get - { - return _dict.Keys; - } - } - public ICollection Values - { - get - { - return _dict.Values; - } - } - - - public string this[string key] - { - get - { - return _dict[Normalize(key)]; - } - set - { - _dict[Normalize(key)] = value; - } - } - - - private string Normalize(string header) - { - if (!_asIs) - { - header = header.ToLower(); - } - header = header.Trim(); - if (!Validator.IsMatch(header)) - { - throw new ArgumentException("Invalid header: " + header); - } - return header; - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - public void Add(string key, string value) - { - _dict.Add(Normalize(key), value); - } - - public void Clear() - { - _dict.Clear(); - } - - public bool Contains(KeyValuePair item) - { - var p = new KeyValuePair(Normalize(item.Key), item.Value); - return _dict.Contains(p); - } - - public bool ContainsKey(string key) - { - return _dict.ContainsKey(Normalize(key)); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public IEnumerator> GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public bool Remove(string key) - { - return _dict.Remove(Normalize(key)); - } - - public bool Remove(KeyValuePair item) - { - return Remove(item.Key); - } - - public override string ToString() - { - return string.Format("({0})", string.Join(", ", (from x in _dict - select string.Format("{0}={1}", x.Key, x.Value)))); - } - - public bool TryGetValue(string key, out string value) - { - return _dict.TryGetValue(Normalize(key), out value); - } - - public string GetValueOrDefault(string key, string defaultValue) - { - string val; - - if (TryGetValue(key, out val)) - { - return val; - } - - return defaultValue; - } - } -} diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 4ce047172..3092589c1 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Xml; using Emby.Dlna.Didl; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Xml; namespace Emby.Dlna.Service @@ -185,8 +186,7 @@ namespace Emby.Dlna.Service { using (var subReader = reader.ReadSubtree()) { - result.Headers = ParseFirstBodyChild(subReader); - + ParseFirstBodyChild(subReader, result.Headers); return result; } } @@ -204,10 +204,8 @@ namespace Emby.Dlna.Service return result; } - private Headers ParseFirstBodyChild(XmlReader reader) + private void ParseFirstBodyChild(XmlReader reader, IDictionary headers) { - var result = new Headers(); - reader.MoveToContent(); reader.Read(); @@ -216,25 +214,24 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.Add(reader.LocalName, reader.ReadElementContentAsString()); + // TODO: Should we be doing this here, or should it be handled earlier when decoding the request? + headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString(); } else { reader.Read(); } } - - return result; } private class ControlRequestInfo { public string LocalName; public string NamespaceURI; - public Headers Headers = new Headers(); + public IDictionary Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); } - protected abstract IEnumerable> GetResult(string methodName, Headers methodParams); + protected abstract IEnumerable> GetResult(string methodName, IDictionary methodParams); private void LogRequest(ControlRequest request) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 8a5ae2c3a..0e1f5a551 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -518,7 +518,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var handler = HttpHandlerFactory.GetHandler(httpReq); + var handler = HttpHandlerFactory.GetHandler(httpReq, _logger); if (handler != null) { diff --git a/ServiceStack/HttpHandlerFactory.cs b/ServiceStack/HttpHandlerFactory.cs index d48bfeb5f..5f4892d51 100644 --- a/ServiceStack/HttpHandlerFactory.cs +++ b/ServiceStack/HttpHandlerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -9,12 +10,16 @@ namespace ServiceStack public class HttpHandlerFactory { // Entry point for HttpListener - public static RestHandler GetHandler(IHttpRequest httpReq) + public static RestHandler GetHandler(IHttpRequest httpReq, ILogger logger) { var pathInfo = httpReq.PathInfo; var pathParts = pathInfo.TrimStart('/').Split('/'); - if (pathParts.Length == 0) return null; + if (pathParts.Length == 0) + { + logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl); + return null; + } string contentType; var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType); -- cgit v1.2.3 From 598f1cf2bddebbd167ed70470e54a2504ca4c0a7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 4 Dec 2016 16:54:45 -0500 Subject: update url matching --- ServiceStack/Host/RestPath.cs | 13 +++++++++---- ServiceStack/Host/ServiceController.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'ServiceStack') diff --git a/ServiceStack/Host/RestPath.cs b/ServiceStack/Host/RestPath.cs index 7222578a9..5bbd03a21 100644 --- a/ServiceStack/Host/RestPath.cs +++ b/ServiceStack/Host/RestPath.cs @@ -109,7 +109,7 @@ namespace ServiceStack.Host this.Notes = notes; this.restPath = path; - this.allowsAllVerbs = verbs == null || verbs == WildCard; + this.allowsAllVerbs = verbs == null || string.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase); if (!this.allowsAllVerbs) { this.allowedVerbs = verbs.ToUpper(); @@ -123,7 +123,7 @@ namespace ServiceStack.Host { if (string.IsNullOrEmpty(component)) continue; - if (component.Contains(VariablePrefix) + if (StringContains(component, VariablePrefix) && component.IndexOf(ComponentSeperator) != -1) { hasSeparators.Add(true); @@ -240,12 +240,17 @@ namespace ServiceStack.Host score += Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY - var exactVerb = httpMethod == AllowedVerbs; + var exactVerb = string.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase); score += exactVerb ? 10 : 1; return score; } + private bool StringContains(string str1, string str2) + { + return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1; + } + /// /// For performance withPathInfoParts should already be a lower case string /// to minimize redundant matching operations. @@ -259,7 +264,7 @@ namespace ServiceStack.Host wildcardMatchCount = 0; if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false; - if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false; + if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod)) return false; if (!ExplodeComponents(ref withPathInfoParts)) return false; if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false; diff --git a/ServiceStack/Host/ServiceController.cs b/ServiceStack/Host/ServiceController.cs index 7eb1253b3..378c21d5d 100644 --- a/ServiceStack/Host/ServiceController.cs +++ b/ServiceStack/Host/ServiceController.cs @@ -83,7 +83,7 @@ namespace ServiceStack.Host } } - public readonly Dictionary> RestPathMap = new Dictionary>(); + public readonly Dictionary> RestPathMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); public void RegisterRestPaths(Type requestType) { -- cgit v1.2.3