ServiceStack REST API路径变量来自root抛出异常

时间:2013-07-12 11:34:40

标签: asp.net rest servicestack

我正在尝试使用ServiceStack编写REST Web服务,该服务接受路径之外的可变路径。例如:

[Route("/{group}"]
public class Entity : IReturn<SomeType> {}

这会抛出NotSupported Exception“不支持类型Entity上的”RestPath'/ {collection}'。但是,如果我将以下路径(以及AppHost配置中的关联路径)更改为:

[Route("/rest/{group}"]

它运作得很好。为了与我正在使用的系统集成,我需要使用/ {group}。

2 个答案:

答案 0 :(得分:3)

ServiceStack现在允许您从/根路径添加fallback route来处理任何未匹配的请求,这些请求不是由catch-all处理程序处理的,也不是指现有的静态文件。所以在v3.9.56中你现在可以做到:

[FallbackRoute("/{group}"]
public class Entity : IReturn<SomeType> {}

另一种选择是注册IAppHost.CatchAllHandlers来处理不匹配的路由,但是您需要返回自己的IHttpHandler来处理请求,或者返回RedirectHttpHandler来重定向到由ServiceStack管理的其他路由。

答案 1 :(得分:3)

我目前正在进行的工作,一个在不更改浏览器中的网址的情况下为所有“未找到”路由提供默认页面的插件,包含了处理全局通配符路由所需的大部分内容。用它来帮助你入门。

要了解此代码的作用,有助于了解ServiceStack的路由优先级,以及CatchAllHandler如何适应该流程。 ServiceStack调用ServiceStackHttpHandlerFactory.GetHandler来获取当前路由的处理程序。

ServiceStackHttpHandlerFactory.GetHandler返回:

  1. 匹配的RawHttpHandler,如果有的话。
  2. 如果是域根,则GetCatchAllHandlerIfAny(...),返回的处理程序(如果有)。
  3. 如果路线与元数据uri 匹配(我正在跳过这里的确切逻辑,因为它对你的问题不重要),相关处理程序(如果有的话)。
  4. ServiceStackHttpHandlerFactory.GetHandlerForPathInfo返回的处理程序(如果有)。
  5. NotFoundHandler。
  6. ServiceStackHttpHandlerFactory.GetHandlerForPathInfo返回:

    1. 如果url匹配有效的REST路由,则使用新的RestHandler。
    2. 如果网址与现有文件或目录匹配,则返回
      • GetCatchAllHandlerIfAny(...),返回的处理程序(如果有的话)。
      • 如果它是受支持的文件类型,则为StaticFileHandler,
      • 如果它不是受支持的文件类型,则为ForbiddenHttpHandler。
    3. GetCatchAllHandlerIfAny(...),返回的处理程序(如果有)。
    4. 空。
    5. CatchAllHandlers数组包含评估url并返回处理程序或null的函数。数组中的函数按顺序调用,第一个不返回null的函数处理路由。让我强调一些关键要素:

      首先,插件在注册时向appHost.CatchAllHandlers数组添加CatchAllHandler。

          public void Register(IAppHost appHost)
          {
              appHost.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
                                              Factory(method, pathInfo, filepath));
          }
      

      第二,CatchAllHandler。如上所述,可以为域根,现有文件或目录或任何其他不匹配的路由调用该功能。如果符合条件,您的方法应返回处理程序,或返回null。

          private static Html5ModeFeature Factory(String method, String pathInfo, String filepath)
          {
              var Html5ModeHandler = Html5ModeFeature.Instance;
              List<string> WebHostRootFileNames = RootFiles();
      
              // handle domain root
              if (string.IsNullOrEmpty(pathInfo) || pathInfo == "/")
              {
                  return Html5ModeHandler;
              }
      
              // don't handle 'mode' urls
              var mode = EndpointHost.Config.ServiceStackHandlerFactoryPath;
              if (mode != null && pathInfo.EndsWith(mode))
              {
                  return null;
              }
      
              var pathParts = pathInfo.TrimStart('/').Split('/');
              var existingFile = pathParts[0].ToLower();
              var catchAllHandler = new Object();
      
              if (WebHostRootFileNames.Contains(existingFile))
              {
                  var fileExt = Path.GetExtension(filepath);
                  var isFileRequest = !string.IsNullOrEmpty(fileExt);
      
                  // don't handle directories or files that have another handler
                  catchAllHandler = GetCatchAllHandlerIfAny(method, pathInfo, filepath);
                  if (catchAllHandler != null) return null;
      
                  // don't handle existing files under any event
                  return isFileRequest ? null : Html5ModeHandler;
              }
      
              // don't handle non-physical urls that have another handler
              catchAllHandler = GetCatchAllHandlerIfAny(method, pathInfo, filepath);
              if (catchAllHandler != null) return null;
      
              // handle anything else
              return Html5ModeHandler;
          }
      

      对于根域中的通配符,您可能不希望劫持可由另一个CatchAllHandler处理的路由。如果是这样,为了避免无限递归,您需要一个自定义GetCatchAllHandlerIfAny方法。

          //
          // local copy of ServiceStackHttpHandlerFactory.GetCatchAllHandlerIfAny, prevents infinite recursion
          //
          private static IHttpHandler GetCatchAllHandlerIfAny(string httpMethod, string pathInfo, string filePath)
          {
              if (EndpointHost.CatchAllHandlers != null)
              {
                  foreach (var httpHandlerResolver in EndpointHost.CatchAllHandlers)
                  {
                      if (httpHandlerResolver == Html5ModeFeature.Factory) continue; // avoid infinite recursion
      
                      var httpHandler = httpHandlerResolver(httpMethod, pathInfo, filePath);
                      if (httpHandler != null)
                          return httpHandler;
                  }
              }
      
              return null;
          }
      

      这是完整且完全未经测试的插件。它汇编。它不保证适用于任何特定目的。

      using ServiceStack;
      using ServiceStack.Common.Web;
      using ServiceStack.Razor;
      using ServiceStack.ServiceHost;
      using ServiceStack.Text;
      using ServiceStack.WebHost.Endpoints;
      using ServiceStack.WebHost.Endpoints.Formats;
      using ServiceStack.WebHost.Endpoints.Support;
      using ServiceStack.WebHost.Endpoints.Support.Markdown;
      using System;
      using System.Collections.Generic;
      using System.IO;
      using System.Text;
      using System.Web;
      
      namespace MyProject.Support
      {
      public enum DefaultFileFormat
      {
          Markdown,
          Razor,
          Static
      }
      
      public class Html5ModeFeature : EndpointHandlerBase, IPlugin
      {
          private FileInfo fi { get; set; }
          private DefaultFileFormat FileFormat { get; set; }
          private DateTime FileModified { get; set; }
          private byte[] FileContents { get; set; }
          public MarkdownHandler Markdown { get; set; }
          public RazorHandler Razor { get; set; }
          public object Model { get; set; }
          private static Dictionary<string, string> allDirs;
      
          public string PathInfo { get; set; }
      
          public void Register(IAppHost appHost)
          {
              appHost.CatchAllHandlers.Add((string method, string pathInfo, string filepath) =>
                                              Factory(method, pathInfo, filepath));
          }
      
          private Html5ModeFeature()
          {
              foreach (var defaultDoc in EndpointHost.Config.DefaultDocuments)
              {
                  if (PathInfo == null) 
                  {
                      var defaultFileName = Path.Combine(Directory.GetCurrentDirectory(), defaultDoc);
                      if (!File.Exists(defaultFileName)) continue;
                      PathInfo = (String)defaultDoc; // use first default document found.
                  }
              }
              SetFile();
          }
      
          private static Html5ModeFeature instance;
          public static Html5ModeFeature Instance
          {
              get { return instance ?? (instance = new Html5ModeFeature()); }
          }
      
          public void SetFile()
          {
              if (PathInfo.EndsWith(MarkdownFormat.MarkdownExt) || PathInfo.EndsWith(MarkdownFormat.TemplateExt))
              {
                  Markdown = new MarkdownHandler(PathInfo);
                  FileFormat = DefaultFileFormat.Markdown;
                  return;
              }
              if (PathInfo.EndsWith(Razor.RazorFormat.RazorFileExtension)) {
                  Razor = new RazorHandler(PathInfo);
                  FileFormat = DefaultFileFormat.Razor;
                  return;
              }
              FileContents = File.ReadAllBytes(PathInfo);
              FileModified = File.GetLastWriteTime(PathInfo);
              FileFormat = DefaultFileFormat.Static;
          }
      
          //
          // ignore request.PathInfo, return default page, extracted from StaticFileHandler.ProcessResponse
          //
          public void ProcessStaticPage(IHttpRequest request, IHttpResponse response, string operationName)
          {
              response.EndHttpHandlerRequest(skipClose: true, afterBody: r =>
              {
      
                  TimeSpan maxAge;
                  if (r.ContentType != null && EndpointHost.Config.AddMaxAgeForStaticMimeTypes.TryGetValue(r.ContentType, out maxAge))
                  {
                      r.AddHeader(HttpHeaders.CacheControl, "max-age=" + maxAge.TotalSeconds);
                  }
      
                  if (request.HasNotModifiedSince(fi.LastWriteTime))
                  {
                      r.ContentType = MimeTypes.GetMimeType(PathInfo);
                      r.StatusCode = 304;
                      return;
                  }
      
                  try
                  {
                      r.AddHeaderLastModified(fi.LastWriteTime);
                      r.ContentType = MimeTypes.GetMimeType(PathInfo);
      
                      if (fi.LastWriteTime > this.FileModified)
                          SetFile(); //reload
      
                      r.OutputStream.Write(this.FileContents, 0, this.FileContents.Length);
                      r.Close();
                      return;
                  }
                  catch (Exception ex)
                  {
                      throw new HttpException(403, "Forbidden.");
                  }
              });
          }
      
          private void ProcessServerError(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
          {
              var sb = new StringBuilder();
              sb.AppendLine("{");
              sb.AppendLine("\"ResponseStatus\":{");
              sb.AppendFormat(" \"ErrorCode\":{0},\n", 500);
              sb.AppendFormat(" \"Message\": HTML5ModeHandler could not serve file {0}.\n", PathInfo.EncodeJson());
              sb.AppendLine("}");
              sb.AppendLine("}");
      
              httpRes.EndHttpHandlerRequest(skipClose: true, afterBody: r =>
              {
                  r.StatusCode = 500;
                  r.ContentType = ContentType.Json;
                  var sbBytes = sb.ToString().ToUtf8Bytes();
                  r.OutputStream.Write(sbBytes, 0, sbBytes.Length);
                  r.Close();
              });
              return;
          }
      
          private static List<string> RootFiles()
          {
              var WebHostPhysicalPath = EndpointHost.Config.WebHostPhysicalPath;
              List<string> WebHostRootFileNames = new List<string>();
      
              foreach (var filePath in Directory.GetFiles(WebHostPhysicalPath))
              {
                  var fileNameLower = Path.GetFileName(filePath).ToLower();
                  WebHostRootFileNames.Add(Path.GetFileName(fileNameLower));
              }
              foreach (var dirName in Directory.GetDirectories(WebHostPhysicalPath))
              {
                  var dirNameLower = Path.GetFileName(dirName).ToLower();
                  WebHostRootFileNames.Add(Path.GetFileName(dirNameLower));
              }
              return WebHostRootFileNames;
          }
      
      
          private static Html5ModeFeature Factory(String method, String pathInfo, String filepath)
          {
              var Html5ModeHandler = Html5ModeFeature.Instance;
              List<string> WebHostRootFileNames = RootFiles();
      
              // handle domain root
              if (string.IsNullOrEmpty(pathInfo) || pathInfo == "/")
              {
                  return Html5ModeHandler;
              }
      
              // don't handle 'mode' urls
              var mode = EndpointHost.Config.ServiceStackHandlerFactoryPath;
              if (mode != null && pathInfo.EndsWith(mode))
              {
                  return null;
              }
      
              var pathParts = pathInfo.TrimStart('/').Split('/');
              var existingFile = pathParts[0].ToLower();
              var catchAllHandler = new Object();
      
              if (WebHostRootFileNames.Contains(existingFile))
              {
                  var fileExt = Path.GetExtension(filepath);
                  var isFileRequest = !string.IsNullOrEmpty(fileExt);
      
                  // don't handle directories or files that have another handler
                  catchAllHandler = GetCatchAllHandlerIfAny(method, pathInfo, filepath);
                  if (catchAllHandler != null) return null;
      
                  // don't handle existing files under any event
                  return isFileRequest ? null : Html5ModeHandler;
              }
      
              // don't handle non-physical urls that have another handler
              catchAllHandler = GetCatchAllHandlerIfAny(method, pathInfo, filepath);
              if (catchAllHandler != null) return null;
      
              // handle anything else
              return Html5ModeHandler;
          }
      
          //
          // Local copy of private StaticFileHandler.DirectoryExists
          //
          public static bool DirectoryExists(string dirPath, string appFilePath)
          {
              if (dirPath == null) return false;
      
              try
              {
                  if (!ServiceStack.Text.Env.IsMono)
                      return Directory.Exists(dirPath);
              }
              catch
              {
                  return false;
              }
      
              if (allDirs == null)
                  allDirs = CreateDirIndex(appFilePath);
      
              var foundDir = allDirs.ContainsKey(dirPath.ToLower());
      
              //log.DebugFormat("Found dirPath {0} in Mono: ", dirPath, foundDir);
      
              return foundDir;
          }
          //
          // Local copy of private StaticFileHandler.CreateDirIndex
          //
          static Dictionary<string, string> CreateDirIndex(string appFilePath)
          {
              var indexDirs = new Dictionary<string, string>();
      
              foreach (var dir in GetDirs(appFilePath))
              {
                  indexDirs[dir.ToLower()] = dir;
              }
      
              return indexDirs;
          }
          //
          // Local copy of private StaticFileHandler.GetDirs
          //
          static List<string> GetDirs(string path)
          {
              var queue = new Queue<string>();
              queue.Enqueue(path);
      
              var results = new List<string>();
      
              while (queue.Count > 0)
              {
                  path = queue.Dequeue();
                  try
                  {
                      foreach (string subDir in Directory.GetDirectories(path))
                      {
                          queue.Enqueue(subDir);
                          results.Add(subDir);
                      }
                  }
                  catch (Exception ex)
                  {
                      Console.Error.WriteLine(ex);
                  }
              }
      
              return results;
          }
          //
          // local copy of ServiceStackHttpHandlerFactory.GetCatchAllHandlerIfAny, prevents infinite recursion
          //
          private static IHttpHandler GetCatchAllHandlerIfAny(string httpMethod, string pathInfo, string filePath)
          {
              if (EndpointHost.CatchAllHandlers != null)
              {
                  foreach (var httpHandlerResolver in EndpointHost.CatchAllHandlers)
                  {
                      if (httpHandlerResolver == Html5ModeFeature.Factory) continue; // avoid infinite recursion
      
                      var httpHandler = httpHandlerResolver(httpMethod, pathInfo, filePath);
                      if (httpHandler != null)
                          return httpHandler;
                  }
              }
      
              return null;
          }
      
          public override void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
          {
              switch (FileFormat)
              {
                  case DefaultFileFormat.Markdown: 
                  {
                      Markdown.ProcessRequest(httpReq, httpRes, operationName);
                      break;
                  }
                  case DefaultFileFormat.Razor:
                  {
                      Razor.ProcessRequest(httpReq, httpRes, operationName);
                      break;
                  }
                  case DefaultFileFormat.Static:
                  {
                      fi.Refresh();
                      if (fi.Exists) ProcessStaticPage(httpReq, httpRes, operationName); else  ProcessServerError(httpReq, httpRes, operationName); 
                      break;
                  }
                  default:
                  {
                      ProcessServerError(httpReq, httpRes, operationName);
                      break;
                  }
              }            
          }   
      
      
          public override object CreateRequest(IHttpRequest request, string operationName)
          {
              return null;
          }
      
          public override object GetResponse(IHttpRequest httpReq, IHttpResponse httpRes, object request)
          {
              return null;
          }
      
      }       
      }