private Router.Dest AddController(WebApp.HTTP httpMethod, string path, Type cls, string action, IList <string> names) { try { // Look for the method in all public methods declared in the class // or inherited by the class. // Note: this does not distinguish methods with the same signature // but different return types. // TODO: We may want to deal with methods that take parameters in the future MethodInfo method = cls.GetMethod(action, null); Router.Dest dest = routes[path]; if (dest == null) { // avoid any runtime checks dest = new Router.Dest(path, method, cls, names, httpMethod); routes[path] = dest; return(dest); } dest.methods.AddItem(httpMethod); return(dest); } catch (MissingMethodException) { throw new WebAppException(action + "() not found in " + cls); } catch (SecurityException) { throw new WebAppException("Security exception thrown for " + action + "() in " + cls); } }
// starting point to look for default classes // path->dest /// <summary>Add a route to the router.</summary> /// <remarks> /// Add a route to the router. /// e.g., add(GET, "/foo/show", FooController.class, "show", [name...]); /// The name list is from /foo/show/:name/... /// </remarks> internal virtual Router.Dest Add(WebApp.HTTP httpMethod, string path, Type cls, string action, IList <string> names) { lock (this) { Log.Debug("adding {}({})->{}#{}", new object[] { path, names, cls, action }); Router.Dest dest = AddController(httpMethod, path, cls, action, names); AddDefaultView(dest); return(dest); } }
private void AddDefaultView(Router.Dest dest) { string controllerName = dest.controllerClass.Name; if (controllerName.EndsWith("Controller")) { controllerName = Sharpen.Runtime.Substring(controllerName, 0, controllerName.Length - 10); } dest.defaultViewClass = Find <View>(dest.controllerClass.Assembly.GetName(), StringHelper.Join (controllerName + "View")); }
private Router.Dest LookupRoute(WebApp.HTTP method, string path) { string key = path; do { Router.Dest dest = routes[key]; if (dest != null && MethodAllowed(method, dest)) { if ((object)key == path) { // shut up warnings Log.Debug("exact match for {}: {}", key, dest.action); return(dest); } else { if (IsGoodMatch(dest, path)) { Log.Debug("prefix match2 for {}: {}", key, dest.action); return(dest); } } return(ResolveAction(method, dest, path)); } KeyValuePair <string, Router.Dest> lower = routes.LowerEntry(key); if (lower == null) { return(null); } dest = lower.Value; if (PrefixMatches(dest, path)) { if (MethodAllowed(method, dest)) { if (IsGoodMatch(dest, path)) { Log.Debug("prefix match for {}: {}", lower.Key, dest.action); return(dest); } return(ResolveAction(method, dest, path)); } // check other candidates int slashPos = key.LastIndexOf('/'); key = slashPos > 0 ? Sharpen.Runtime.Substring(path, 0, slashPos) : "/"; } else { key = "/"; } }while (true); }
/// <summary>Resolve a path to a destination.</summary> internal virtual Router.Dest Resolve(string httpMethod, string path) { lock (this) { WebApp.HTTP method = WebApp.HTTP.ValueOf(httpMethod); // can throw Router.Dest dest = LookupRoute(method, path); if (dest == null) { return(ResolveDefault(method, path)); } return(dest); } }
// Dest may contain a candidate controller private Router.Dest ResolveAction(WebApp.HTTP method, Router.Dest dest, string path ) { if (dest.prefix.Length == 1) { return(null); } Preconditions.CheckState(!IsGoodMatch(dest, path), dest.prefix); Preconditions.CheckState(Slash.CountIn(path) > 1, path); IList <string> parts = WebApp.ParseRoute(path); string controller = parts[WebApp.RController]; string action = parts[WebApp.RAction]; return(Add(method, StringHelper.Pjoin(string.Empty, controller, action), dest.controllerClass , action, null)); }
internal static bool IsGoodMatch(Router.Dest dest, string path) { if (Slash.CountIn(dest.prefix) > 1) { return(true); } // We want to match (/foo, :a) for /foo/bar/blah and (/, :a) for /123 // but NOT / for /foo or (/, :a) for /foo or /foo/ because default route // (FooController#index) for /foo and /foo/ takes precedence. if (dest.prefix.Length == 1) { return(dest.pathParams.Count > 0 && !MaybeController(path)); } return(dest.pathParams.Count > 0 || (path.EndsWith("/") && Slash.CountIn(path) == 2)); }
internal static bool PrefixMatches(Router.Dest dest, string path) { Log.Debug("checking prefix {}{} for path: {}", new object[] { dest.prefix, dest.pathParams , path }); if (!path.StartsWith(dest.prefix)) { return(false); } int prefixLen = dest.prefix.Length; if (prefixLen > 1 && path.Length > prefixLen && path[prefixLen] != '/') { return(false); } // prefix is / or prefix is path or prefix/... return(true); }
// /path/foo/bar with /path/:arg1/:arg2 will set {arg1=>foo, arg2=>bar} private void SetMoreParams(Controller.RequestContext rc, string pathInfo, Router.Dest dest) { Preconditions.CheckState(pathInfo.StartsWith(dest.prefix), "prefix should match"); if (dest.pathParams.Count == 0 || dest.prefix.Length == pathInfo.Length) { return; } string[] parts = Iterables.ToArray <string>(WebApp.pathSplitter.Split(Sharpen.Runtime.Substring (pathInfo, dest.prefix.Length))); Log.Debug("parts={}, params={}", parts, dest.pathParams); for (int i = 0; i < dest.pathParams.Count && i < parts.Length; ++i) { string key = dest.pathParams[i]; if (key[0] == ':') { rc.MoreParams()[Sharpen.Runtime.Substring(key, 1)] = parts[i]; } } }
/// <exception cref="Javax.Servlet.ServletException"/> /// <exception cref="System.IO.IOException"/> protected override void Service(HttpServletRequest req, HttpServletResponse res) { res.SetCharacterEncoding("UTF-8"); string uri = HtmlQuoting.QuoteHtmlChars(req.GetRequestURI()); if (uri == null) { uri = "/"; } if (devMode && uri.Equals("/__stop")) { // quick hack to restart servers in dev mode without OS commands res.SetStatus(res.ScNoContent); Log.Info("dev mode restart requested"); PrepareToExit(); return; } // if they provide a redirectPath go there instead of going to // "/" so that filters can differentiate the webapps. if (uri.Equals("/")) { string redirectPath = webApp.GetRedirectPath(); if (redirectPath != null && !redirectPath.IsEmpty()) { res.SendRedirect(redirectPath); return; } } string method = req.GetMethod(); if (method.Equals("OPTIONS")) { DoOptions(req, res); return; } if (method.Equals("TRACE")) { DoTrace(req, res); return; } if (method.Equals("HEAD")) { DoGet(req, res); // default to bad request return; } string pathInfo = req.GetPathInfo(); if (pathInfo == null) { pathInfo = "/"; } Controller.RequestContext rc = injector.GetInstance <Controller.RequestContext>(); if (SetCookieParams(rc, req) > 0) { Cookie ec = rc.Cookies()[ErrorCookie]; if (ec != null) { rc.SetStatus(System.Convert.ToInt32(rc.Cookies()[StatusCookie].GetValue())); RemoveErrorCookies(res, uri); rc.Set(Params.ErrorDetails, ec.GetValue()); Render(typeof(ErrorPage)); return; } } rc.prefix = webApp.Name(); Router.Dest dest = null; try { dest = router.Resolve(method, pathInfo); } catch (WebAppException e) { rc.error = e; if (!e.Message.Contains("not found")) { rc.SetStatus(res.ScInternalServerError); Render(typeof(ErrorPage)); return; } } if (dest == null) { rc.SetStatus(res.ScNotFound); Render(typeof(ErrorPage)); return; } rc.devMode = devMode; SetMoreParams(rc, pathInfo, dest); Controller controller = injector.GetInstance(dest.controllerClass); try { // TODO: support args converted from /path/:arg1/... dest.action.Invoke(controller, (object[])null); if (!rc.rendered) { if (dest.defaultViewClass != null) { Render(dest.defaultViewClass); } else { if (rc.status == 200) { throw new InvalidOperationException("No view rendered for 200"); } } } } catch (Exception e) { Log.Error("error handling URI: " + uri, e); // Page could be half rendered (but still not flushed). So redirect. RedirectToErrorPage(res, e, uri, devMode); } }
internal static bool MethodAllowed(WebApp.HTTP method, Router.Dest dest) { // Accept all methods by default, unless explicity configured otherwise. return(dest.methods.Contains(method) || (dest.methods.Count == 1 && dest.methods. Contains(WebApp.HTTP.Get))); }