//--- Interface Methods --- int IPlugEndpoint.GetScoreWithNormalizedUri(XUri uri, out XUri normalized) { int result; XUri prefix; lock(_aliases) { _aliases.TryGetValue(uri, out prefix, out result); } // check if we found a match if(prefix != null) { normalized = uri.ChangePrefix(prefix, _localMachineUri); // if 'dream.in.uri' is not set, set it if((normalized.GetParam(DreamInParam.URI, null) == null) && !prefix.Scheme.EqualsInvariant("local")) { normalized = normalized.With(DreamInParam.URI, prefix.ToString()); } } else { normalized = null; } return (result > 0) ? result + Plug.BASE_ENDPOINT_SCORE : 0; }
private Result<DreamMessage> SubmitRequestAsync(string verb, XUri uri, IPrincipal user, DreamMessage request, Result<DreamMessage> response, Action completion) { if(string.IsNullOrEmpty(verb)) { if(completion != null) { completion(); } throw new ArgumentNullException("verb"); } if(uri == null) { if(completion != null) { completion(); } throw new ArgumentNullException("uri"); } if(request == null) { if(completion != null) { completion(); } throw new ArgumentNullException("request"); } if(response == null) { if(completion != null) { completion(); } throw new ArgumentNullException("response"); } // ensure environment is still running if(!IsRunning) { response.Return(DreamMessage.InternalError("host not running")); if(completion != null) { completion(); } return response; } try { Interlocked.Increment(ref _requestCounter); // check if we were not able to begin processing the request DreamMessage failed = BeginRequest(completion, uri, request); if(failed != null) { response.Return(failed); EndRequest(completion, uri, request); return response; } // check if 'verb' is overwritten by a processing parameter verb = verb.ToUpperInvariant(); string requestedVerb = (uri.GetParam(DreamInParam.VERB, null) ?? request.Headers.MethodOverride ?? verb).ToUpperInvariant(); if( verb.EqualsInvariant(Verb.POST) || ( verb.EqualsInvariant(Verb.GET) && ( requestedVerb.EqualsInvariant(Verb.OPTIONS) || requestedVerb.EqualsInvariant(Verb.HEAD) ) ) ) { verb = requestedVerb; } // check if an origin was specified request.Headers.DreamOrigin = uri.GetParam(DreamInParam.ORIGIN, request.Headers.DreamOrigin); // check if a public uri is supplied XUri publicUri = XUri.TryParse(uri.GetParam(DreamInParam.URI, null) ?? request.Headers.DreamPublicUri); XUri transport = XUri.TryParse(request.Headers.DreamTransport) ?? uri.WithoutCredentialsPathQueryFragment(); if(publicUri == null) { // check if request is local if(transport.Scheme.EqualsInvariantIgnoreCase("local")) { // local:// uris with no public-uri specifier default to the configured public-uri publicUri = _publicUri; } else { // check if the request was forwarded through Apache mod_proxy string proxyOverride = uri.GetParam(DreamInParam.HOST, null); if(string.IsNullOrEmpty(proxyOverride)) { proxyOverride = request.Headers.ForwardedHost; } string serverPath = string.Join("/", transport.Segments); if(proxyOverride != null) { // request used an override, append path of public-uri serverPath = string.Join("/", _publicUri.Segments); } // set the uri scheme based-on the incoming scheme and the override header string scheme = transport.Scheme; if("On".EqualsInvariantIgnoreCase(request.Headers.FrontEndHttps ?? "")) { scheme = Scheme.HTTPS; } scheme = uri.GetParam(DreamInParam.SCHEME, scheme); // set the host port string hostPort = proxyOverride ?? request.Headers.Host ?? uri.HostPort; publicUri = new XUri(string.Format("{0}://{1}", scheme, hostPort)).AtPath(serverPath); } request.Headers.DreamPublicUri = publicUri.ToString(); } // set host header request.Headers.Host = publicUri.HostPort; // convert incoming uri to local:// XUri localFeatureUri = uri.ChangePrefix(uri.WithoutPathQueryFragment(), _localMachineUri); // check if path begins with public uri path if((transport.Segments.Length > 0) && localFeatureUri.PathStartsWith(transport.Segments)) { localFeatureUri = localFeatureUri.WithoutFirstSegments(transport.Segments.Length); } // check if the path is the application root and whether we have special behavior for that if(localFeatureUri.Path.IfNullOrEmpty("/") == "/") { if(!string.IsNullOrEmpty(_rootRedirect)) { localFeatureUri = localFeatureUri.AtAbsolutePath(_rootRedirect); } else if(IsDebugEnv) { localFeatureUri = localFeatureUri.AtAbsolutePath("/host/services"); } } // find the requested feature List<DreamFeature> features; lock(_features) { features = _features.Find(localFeatureUri); } DreamFeature feature = null; if(features != null) { // TODO (steveb): match the incoming mime-type to the feature's acceptable mime-types (mime-type overloading) // match the request verb to the feature verb foreach(DreamFeature entry in features) { if((entry.Verb == "*") || entry.Verb.EqualsInvariant(verb)) { feature = entry; break; } } // check if this is an OPTIONS request and there is no defined feature for it if(verb.EqualsInvariant(Verb.OPTIONS) && ((feature == null) || feature.Verb.EqualsInvariant("*"))) { // list all allowed methods List<string> methods = new List<string>(); foreach(DreamFeature entry in features) { if(!methods.Contains(entry.Verb)) { methods.Add(entry.Verb); } } methods.Sort(StringComparer.Ordinal.Compare); DreamMessage result = DreamMessage.Ok(); result.Headers.Allow = string.Join(", ", methods.ToArray()); response.Return(result); // decrease counter for external requests EndRequest(completion, uri, request); return response; } } // check if a feature was found if(feature == null) { DreamMessage result; // check if any feature was found if((features == null) || (features.Count == 0)) { string msg = verb + " URI: " + uri.ToString(false) + " LOCAL: " + localFeatureUri.ToString(false) + " PUBLIC: " + publicUri + " TRANSPORT: " + transport; _log.WarnMethodCall("ProcessRequest: feature not found", msg); result = DreamMessage.NotFound("resource not found"); } else { string msg = verb + " " + uri.ToString(false); _log.WarnMethodCall("ProcessRequest: method not allowed", msg); List<string> methods = new List<string>(); foreach(DreamFeature entry in features) { if(!methods.Contains(entry.Verb)) { methods.Add(entry.Verb); } } methods.Sort(StringComparer.Ordinal.Compare); result = DreamMessage.MethodNotAllowed(methods.ToArray(), "allowed methods are " + string.Join(", ", methods.ToArray())); } response.Return(result); // decrease counter for external requests EndRequest(completion, uri, request); return response; } // add uri to aliases list if(_memorizeAliases) { lock(_aliases) { _aliases[transport] = transport; _aliases[publicUri] = publicUri; } } // create context DreamContext context = new DreamContext(this, verb, localFeatureUri, feature, publicUri, _publicUri, request, CultureInfo.InvariantCulture, GetRequestLifetimeScopeFactory(feature.Service)); // attach request id to the context context.SetState(DreamHeaders.DREAM_REQUEST_ID, request.Headers.DreamRequestId); // add user to context context.User = user; // build linked-list of feature calls var chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(result => { // extract message DreamMessage message; if(result.HasValue) { message = result.Value; } else if(result.Exception is DreamAbortException) { message = ((DreamAbortException)result.Exception).Response; } else if(result.Exception is DreamCachedResponseException) { message = ((DreamCachedResponseException)result.Exception).Response; } else { _log.ErrorExceptionFormat(response.Exception, "Failed Feature '{0}' Chain [{1}:{2}]: {3}", feature.MainStage.Name, verb, localFeatureUri.Path, response.Exception.Message ); message = DreamMessage.InternalError(result.Exception); } // decrease counter for external requests EndRequest(completion, uri, request); // need to manually dispose of the context, since we're already attaching and detaching it by hand to TaskEnvs throughout the chain if(response.IsCanceled) { _log.DebugFormat("response for '{0}' has already returned", context.Uri.Path); response.ConfirmCancel(); ((ITaskLifespan)context).Dispose(); } else { ((ITaskLifespan)context).Dispose(); response.Return(message); } }); for(int i = feature.Stages.Length - 1; i >= 0; --i) { var link = new DreamFeatureChain(feature.Stages[i], i == feature.MainStageIndex, context, chain, (i > 0) ? feature.Stages[i - 1].Name : "first"); chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(link.Handler); } // kick-off new task AsyncUtil.Fork( () => chain.Return(request), TaskEnv.New(TimerFactory), new Result(TimeSpan.MaxValue, response.Env).WhenDone(res => { if(!res.HasException) { return; } _log.ErrorExceptionFormat(res.Exception, "handler for {0}:{1} failed", context.Verb, context.Uri.ToString(false)); ((ITaskLifespan)context).Dispose(); // forward exception to recipient response.Throw(res.Exception); // decrease counter for external requests EndRequest(completion, uri, request); }) ); } catch(Exception e) { response.Throw(e); EndRequest(completion, uri, request); } return response; }