├── TinyHttpService.Sample ├── public │ ├── css │ │ └── style.css │ └── test.html ├── file │ └── 8.PNG ├── favicon.ico ├── App.config ├── User.cs ├── view │ └── razor.cshtml ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── TinyHttpService.Sample.csproj ├── TinyHttpService.Test ├── packages.config ├── RequestParser │ ├── HttpRequestParserTest.cs │ ├── JsonBodyDataParseCommandTest.cs │ └── MultiPartFormDataParserTest.cs ├── Utils │ ├── SubsenquenceFinderTest.cs │ └── RebufferableStreamReaderTest.cs ├── Properties │ └── AssemblyInfo.cs ├── Router │ └── RouteHandlerTest.cs └── TinyHttpService.Test.csproj ├── TinyHttpService ├── packages.config ├── TinyHttpServiceConfig.cs ├── Core │ ├── IHttpServiceHandler.cs │ ├── IHttpService.cs │ ├── HttpServiceHandler.cs │ └── HttpService.cs ├── HttpData │ ├── HttpContext.cs │ ├── FilePart.cs │ ├── HttpRequest.cs │ ├── HttpHeader.cs │ ├── Mime.cs │ ├── HttpRequestBody.cs │ ├── HttpResponse.cs │ └── HttpStatusCodes.cs ├── ActionResults │ ├── ActionResult.cs │ ├── Http404NotFoundResult.cs │ ├── ContentResult.cs │ ├── JsonResult.cs │ ├── ViewResult.cs │ ├── DownloadResult.cs │ └── StaticResourceResult.cs ├── RequestParser │ ├── IHttpRequestParser.cs │ ├── Exceptions │ │ └── HttpRequestParseException.cs │ ├── Commands │ │ ├── RequestBodyDataParseCommand.cs │ │ ├── NonBodyDataParseCommand.cs │ │ ├── MultiPartFormDataParseCommand.cs │ │ ├── JsonBodyDataParseCommand.cs │ │ └── UrlEncodedBodyDataParseCommand.cs │ ├── BodyParseCommandFactory.cs │ ├── HttpRequestParser.cs │ └── MultiPartFormDataParser.cs ├── Router │ ├── IRouteHandler.cs │ ├── RouteData.cs │ ├── RouteTable.cs │ └── RouteHandler.cs ├── DefaultTinyHttpServiceFactory.cs ├── Properties │ └── AssemblyInfo.cs ├── Utils │ ├── SubsenquenceFinder.cs │ ├── StreamUtil.cs │ ├── UrlHelper.cs │ └── RebufferableStreamReader.cs └── TinyHttpService.csproj ├── README.md └── TinyHttpService.sln /TinyHttpService.Sample/public/css/style.css: -------------------------------------------------------------------------------- 1 | .grid { 2 | color: yellow; 3 | } -------------------------------------------------------------------------------- /TinyHttpService.Sample/file/8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh6335901/TinyHttpService/HEAD/TinyHttpService.Sample/file/8.PNG -------------------------------------------------------------------------------- /TinyHttpService.Sample/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh6335901/TinyHttpService/HEAD/TinyHttpService.Sample/favicon.ico -------------------------------------------------------------------------------- /TinyHttpService.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

你好

10 | 11 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.Sample 8 | { 9 | public class User 10 | { 11 | public string UserName { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TinyHttpService/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TinyHttpService/TinyHttpServiceConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService 8 | { 9 | public static class TinyHttpServiceConfig 10 | { 11 | public static string StaticFilePath = "/public"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TinyHttpService.Test/RequestParser/HttpRequestParserTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace TinyHttpService.Test.RequestParser 5 | { 6 | [TestClass] 7 | public class HttpRequestParserTest 8 | { 9 | [TestMethod] 10 | public void TestMethod1() 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TinyHttpService/Core/IHttpServiceHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.Core 9 | { 10 | public interface IHttpServiceHandler 11 | { 12 | Task ProcessRequestAsync(Stream stream); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/view/razor.cshtml: -------------------------------------------------------------------------------- 1 | 2 | @model TinyHttpService.Sample.User 3 | 4 | 5 | 6 | 7 | Test Page 8 | 9 | 10 |

@Model.UserName

11 | 12 | 13 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.HttpData 8 | { 9 | public class HttpContext 10 | { 11 | public HttpRequest Request { get; set; } 12 | 13 | public HttpResponse Response { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/ActionResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.HttpData; 7 | 8 | namespace TinyHttpService.ActionResults 9 | { 10 | public abstract class ActionResult 11 | { 12 | public abstract Task ExecuteAsync(HttpContext context); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TinyHttpService/Core/IHttpService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.Core 8 | { 9 | public interface IHttpService : IDisposable 10 | { 11 | /// 12 | /// 监听 13 | /// 14 | /// 端口号 15 | void Bind(int port); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/IHttpRequestParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TinyHttpService.HttpData; 9 | 10 | namespace TinyHttpService.RequestParser 11 | { 12 | public interface IHttpRequestParser 13 | { 14 | Task ParseAsync(Stream stream); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/FilePart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.HttpData 9 | { 10 | public class FilePart 11 | { 12 | public string Filename { get; set; } 13 | 14 | public string Name { get; set; } 15 | 16 | public MemoryStream Data { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Exceptions/HttpRequestParseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.RequestParser 8 | { 9 | public class HttpRequestParseException : Exception 10 | { 11 | public HttpRequestParseException() { } 12 | 13 | public HttpRequestParseException(string message) : base(message) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TinyHttpService/Router/IRouteHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.Router 10 | { 11 | public interface IRouteHandler 12 | { 13 | RouteTable Routes { get; } 14 | 15 | Func Handle(HttpRequest request); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Commands/RequestBodyDataParseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.RequestParser.Commands 10 | { 11 | public abstract class RequestBodyDataParseCommand 12 | { 13 | public abstract Task ExecuteAsync(Stream stream, Encoding e); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.Router; 7 | 8 | namespace TinyHttpService.HttpData 9 | { 10 | public class HttpRequest 11 | { 12 | public string RequestMethod { get; set; } 13 | 14 | public string Uri { get; set; } 15 | 16 | public HttpHeader Header { get; set; } 17 | 18 | public HttpRequestBody Body { get; set; } 19 | 20 | public RouteData RouteData { get; set; } 21 | 22 | public Dictionary QueryString { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Commands/NonBodyDataParseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.RequestParser.Commands 10 | { 11 | public class NonBodyDataParseCommand : RequestBodyDataParseCommand 12 | { 13 | public override async Task ExecuteAsync(Stream stream, Encoding e) 14 | { 15 | var source = new TaskCompletionSource(); 16 | source.SetResult(new HttpRequestBody()); 17 | return await source.Task; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TinyHttpService/DefaultTinyHttpServiceFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.Core; 7 | using TinyHttpService.RequestParser; 8 | using TinyHttpService.Router; 9 | 10 | namespace TinyHttpService 11 | { 12 | public static class DefaultTinyHttpServiceFactory 13 | { 14 | public static HttpService GetDefaultTinyHttpService() 15 | { 16 | IRouteHandler routeHandler = new RouteHandler(); 17 | IHttpRequestParser parser = new HttpRequestParser(); 18 | IHttpServiceHandler handler = new HttpServiceHandler(parser, routeHandler); 19 | 20 | return new HttpService(handler); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/Http404NotFoundResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.ActionResults 10 | { 11 | public class Http404NotFoundResult : ActionResult 12 | { 13 | public override async Task ExecuteAsync(HttpContext context) 14 | { 15 | if (context == null) 16 | { 17 | throw new ArgumentNullException("context"); 18 | } 19 | 20 | var response = context.Response; 21 | response.StatusCode = 404; 22 | response.ContentType = "text/html"; 23 | await response.WriteAsync("

Not Found

"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Commands/MultiPartFormDataParseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.RequestParser.Commands 10 | { 11 | public class MultiPartFormDataParseCommand : RequestBodyDataParseCommand 12 | { 13 | public override async Task ExecuteAsync(Stream stream, Encoding e) 14 | { 15 | MultiPartFormDataParser parser = new MultiPartFormDataParser(stream, e); 16 | await parser.ParseAsync(); 17 | HttpRequestBody body = new HttpRequestBody(); 18 | body.Properties = parser.Parameters; 19 | body.Files = parser.Files; 20 | return body; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TinyHttpService/Router/RouteData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.Router 8 | { 9 | public class RouteData 10 | { 11 | public Dictionary DataTokens { get; private set; } 12 | 13 | public string RouteUri { get; set; } 14 | 15 | public RouteData() 16 | { 17 | this.DataTokens = new Dictionary(); 18 | } 19 | 20 | public string this[string key] 21 | { 22 | get 23 | { 24 | if (DataTokens.ContainsKey(key)) 25 | { 26 | return DataTokens[key]; 27 | } 28 | 29 | return null; 30 | } 31 | set 32 | { 33 | DataTokens[key] = value; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Commands/JsonBodyDataParseCommand.cs: -------------------------------------------------------------------------------- 1 | using ServiceStack.Text; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TinyHttpService.HttpData; 9 | using TinyHttpService.Utils; 10 | 11 | namespace TinyHttpService.RequestParser.Commands 12 | { 13 | public class JsonBodyDataParseCommand : RequestBodyDataParseCommand 14 | { 15 | public override async Task ExecuteAsync(Stream stream, Encoding e) 16 | { 17 | var reader = new StreamReader(stream); 18 | var bodyString = await reader.ReadToEndAsync(); 19 | Dictionary dict = JsonSerializer.DeserializeFromString>(bodyString); 20 | HttpRequestBody body = new HttpRequestBody(); 21 | body.Properties = dict; 22 | return body; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TinyHttpService 2 | =============== 3 | 4 | 简单的Http服务,可以根据http请求进行常见的操作。只需在你的程序(这个程序可以是WPF/Winform/Console等等)中引用并使用,你的程序就变成了一个简单的Http服务器了。这样你的程序就可以根据http请求做你想做的事了,比如:在web页面上操纵你的应用程序,进行文件上传下载等等。 5 | 6 | 使用方法 7 | -------- 8 | 9 | ###首先注册路由: 10 | var routes = RouteTable.Instance; 11 | 12 | routes.Get("/user", (context) => 13 | { 14 | var user = new User(); 15 | user.UserName = "zhang"; 16 | return new ViewResult("/view/razor.cshtml", user); 17 | }); 18 | 19 | routes.Post("/user", (context) => 20 | { 21 | Console.WriteLine(context.Request.Body.ToString()); 22 | 23 | //如果你的程序是wpf程序,需要控制UI元素,你需要使用: 24 | //Element.Dispatcher.Invoke方法或者Elment.Dispatcher.BeginInvoke方法 25 | return new ContentResult("haha"); 26 | }); 27 | 28 | ###然后监听请求: 29 | TinyHttpService service = DefaultTinyHttpServiceFactory.GetDefaultTinyHttpService(); 30 | service.Bind(5000); 31 | 32 | 提醒:如果在你的项目中创建了页面文件和其他的静态文件,你需要将它们输出到bin文件夹下。(右键文件,选择属性,在复制到输出目录下选择总是复制或者复制新文件)。 33 | -------------------------------------------------------------------------------- /TinyHttpService.Test/RequestParser/JsonBodyDataParseCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ServiceStack.Text; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.RequestParser.Commands; 8 | 9 | namespace TinyHttpService.Test.RequestParser 10 | { 11 | [TestClass] 12 | public class JsonBodyDataParseCommandTest 13 | { 14 | [TestMethod] 15 | public async Task CanParseJsonToHttpRequestBody() 16 | { 17 | string json = JsonSerializer.SerializeToString(new { Username = "zhang", Password = "123456", Age = 1 }); 18 | var command = new JsonBodyDataParseCommand(); 19 | 20 | using (var ms = new MemoryStream(Encoding.Default.GetBytes(json))) 21 | { 22 | var body = await command.ExecuteAsync(ms, Encoding.Default); 23 | Assert.AreEqual(body["Age"], "1"); 24 | Assert.AreEqual(body["Username"], "zhang"); 25 | Assert.AreEqual(body["Password"], "123456"); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TinyHttpService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("TinyHttpService")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TinyHttpService")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("8b22b2e9-8590-4f19-ac28-68e956613dd6")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/ContentResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.ActionResults 10 | { 11 | public class ContentResult : ActionResult 12 | { 13 | public string Content { get; set; } 14 | 15 | public ContentResult() { } 16 | 17 | public ContentResult(string content) 18 | { 19 | this.Content = content; 20 | } 21 | 22 | public override async Task ExecuteAsync(HttpContext context) 23 | { 24 | if (context == null) 25 | { 26 | throw new ArgumentNullException("context"); 27 | } 28 | 29 | var response = context.Response; 30 | response.ContentType = "text/plain; charset=utf-8"; 31 | response.StatusCode = 200; 32 | response.AddHeader("Content-Length", Encoding.UTF8.GetByteCount(Content).ToString()); 33 | await response.WriteAsync(Content); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("TinyHttpService.ConsoleTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TinyHttpService.ConsoleTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("68ecbf40-2055-4098-898e-fc212421ebae")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/BodyParseCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.RequestParser.Commands; 8 | 9 | namespace TinyHttpService.RequestParser 10 | { 11 | public class BodyParseCommandFactory 12 | { 13 | public static RequestBodyDataParseCommand GetBodyParseCommand(string contentType) 14 | { 15 | contentType = contentType ?? string.Empty; 16 | contentType = contentType.ToLower(); 17 | 18 | if (contentType.Contains("application/json")) 19 | { 20 | return new JsonBodyDataParseCommand(); 21 | } 22 | else if (contentType.Contains("multipart/form-data")) 23 | { 24 | return new MultiPartFormDataParseCommand(); 25 | } 26 | else if (contentType.Contains("application/x-www-form-urlencoded")) 27 | { 28 | return new UrlEncodedBodyDataParseCommand(); 29 | } 30 | else 31 | { 32 | return new NonBodyDataParseCommand(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.HttpData 8 | { 9 | public class HttpHeader 10 | { 11 | public Dictionary Properties { get; set; } 12 | 13 | public string this[string key] 14 | { 15 | get 16 | { 17 | if (this.Properties.ContainsKey(key)) 18 | { 19 | return this.Properties[key]; 20 | } 21 | 22 | return null; 23 | } 24 | set 25 | { 26 | this.Properties[key] = value; 27 | } 28 | } 29 | 30 | public HttpHeader() 31 | { 32 | this.Properties = new Dictionary(); 33 | } 34 | 35 | public override string ToString() 36 | { 37 | var sb = new StringBuilder(); 38 | foreach (var property in Properties) 39 | { 40 | sb.AppendFormat(@"{0}:{1}{2}", property.Key, property.Value, Environment.NewLine); 41 | } 42 | 43 | return sb.ToString(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/JsonResult.cs: -------------------------------------------------------------------------------- 1 | using ServiceStack.Text; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.ActionResults; 8 | using TinyHttpService.HttpData; 9 | 10 | namespace TinyHttpService.ActionResults 11 | { 12 | public class JsonResult : ActionResult 13 | { 14 | public object Data { get; set; } 15 | 16 | public JsonResult(object data) 17 | { 18 | this.Data = data; 19 | } 20 | 21 | public JsonResult() { } 22 | 23 | public override async Task ExecuteAsync(HttpContext context) 24 | { 25 | if (context == null) 26 | { 27 | throw new ArgumentNullException("context"); 28 | } 29 | 30 | var response = context.Response; 31 | response.ContentType = "application/json; charset=utf-8"; 32 | response.StatusCode = 200; 33 | 34 | if (Data == null) 35 | { 36 | await response.WriteAsync(string.Empty); 37 | return; 38 | } 39 | 40 | var json = JsonSerializer.SerializeToString(Data, Data.GetType()); 41 | response.AddHeader("Content-Length", Encoding.UTF8.GetByteCount(json).ToString()); 42 | await response.WriteAsync(json); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/Mime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.HttpData 8 | { 9 | public static class Mime 10 | { 11 | private static Dictionary mimes = new Dictionary(); 12 | 13 | static Mime() 14 | { 15 | mimes[".jpg"] = "image/jpeg"; 16 | mimes[".png"] = "image/png"; 17 | mimes[".ico"] = "image/ico"; 18 | mimes[".txt"] = "text/plain"; 19 | mimes[".css"] = "text/css"; 20 | mimes[".html"] = "text/html"; 21 | mimes[".htm"] = "text/html"; 22 | mimes[".js"] = "application/javascript"; 23 | mimes[".mp3"] = "audio/mpeg"; 24 | mimes[".mp4"] = "video/mp4"; 25 | mimes[".zip"] = "application/zip"; 26 | mimes[".avi"] = "video/x-msvideo"; 27 | mimes[".bmp"] = "image/bmp"; 28 | mimes[".jpeg"] = "image/jpeg"; 29 | mimes[".wma"] = "audio/x-ms-wma"; 30 | mimes[".wmv"] = "video/x-ms-wmv"; 31 | } 32 | 33 | public static string Get(string ext) 34 | { 35 | if (mimes.ContainsKey(ext)) 36 | { 37 | return mimes[ext]; 38 | } 39 | return null; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpRequestBody.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.HttpData 9 | { 10 | public class HttpRequestBody 11 | { 12 | public Dictionary Properties { get; set; } 13 | 14 | public List Files { get; set; } 15 | 16 | public string this[string key] 17 | { 18 | get 19 | { 20 | if (this.Properties.ContainsKey(key)) 21 | { 22 | return this.Properties[key]; 23 | } 24 | 25 | return null; 26 | } 27 | internal set 28 | { 29 | this.Properties[key] = value; 30 | } 31 | } 32 | 33 | public HttpRequestBody() 34 | { 35 | this.Properties = new Dictionary(); 36 | this.Files = new List(); 37 | } 38 | 39 | public override string ToString() 40 | { 41 | var sb = new StringBuilder(); 42 | foreach (var property in Properties) 43 | { 44 | sb.AppendFormat(@"{0}:{1}{2}", property.Key, property.Value, Environment.NewLine); 45 | } 46 | 47 | return sb.ToString(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /TinyHttpService.Test/Utils/SubsenquenceFinderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using TinyHttpService.Utils; 4 | 5 | namespace TinyHttpService.Test.Utils 6 | { 7 | [TestClass] 8 | public class SubsenquenceFinderTest 9 | { 10 | [TestMethod] 11 | public void ReturnExpectPosWhenSubsenquenceFound() 12 | { 13 | var sub = new byte[] { 15, 20, 22, 23 }; 14 | var sub1 = new byte[] { 11, 20, 22, 23, 15, 15, 20, 22, 23, 222, 111, 100 }; 15 | var full = new byte[] { 11, 20, 22, 23, 15, 15, 20, 22, 23, 222, 111, 100 }; 16 | 17 | int pos = SubsenquenceFinder.Search(full, sub); 18 | int pos1 = SubsenquenceFinder.Search(full, sub1); 19 | Assert.AreEqual(pos, 5); 20 | Assert.AreEqual(pos1, 0); 21 | } 22 | 23 | [TestMethod] 24 | public void ReturnNegativeOneWhenSubsenquenceNotFound() 25 | { 26 | var sub = new byte[] { 15, 20, 22, 23 }; 27 | var sub1 = new byte[] { 11, 20, 22, 23, 15, 15, 21, 22, 23, 222, 111, 100, 2 }; 28 | var full = new byte[] { 11, 20, 22, 23, 15, 15, 21, 22, 23, 222, 111, 100 }; 29 | 30 | int pos = SubsenquenceFinder.Search(full, sub); 31 | int pos1 = SubsenquenceFinder.Search(full, sub1); 32 | Assert.AreEqual(pos, -1); 33 | Assert.AreEqual(pos, -1); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/Commands/UrlEncodedBodyDataParseCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.HttpData; 8 | using TinyHttpService.Utils; 9 | 10 | namespace TinyHttpService.RequestParser.Commands 11 | { 12 | public class UrlEncodedBodyDataParseCommand : RequestBodyDataParseCommand 13 | { 14 | public override async Task ExecuteAsync(Stream stream, Encoding e) 15 | { 16 | string line; 17 | StringBuilder formBodySb = new StringBuilder(); 18 | HttpRequestBody body = new HttpRequestBody(); 19 | 20 | var reader = new StreamReader(stream, e); 21 | while (!string.IsNullOrEmpty(line = await reader.ReadLineAsync())) 22 | { 23 | formBodySb.Append(line); 24 | } 25 | 26 | var bodyString = formBodySb.ToString(); 27 | bodyString = UrlHelper.UrlDecode(bodyString, e); 28 | var bodyProperties = bodyString.Split('&'); 29 | foreach (var bodyProperty in bodyProperties) 30 | { 31 | var keyValuePair = bodyProperty.Split('='); 32 | if (keyValuePair.Length >= 2) 33 | { 34 | body[keyValuePair[0]] = keyValuePair[1]; 35 | } 36 | } 37 | 38 | return body; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TinyHttpService.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TinyHttpService.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TinyHttpService.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8831c754-d5b0-4e28-8272-00127dbefa32")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/ViewResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.HttpData; 8 | using RazorEngine; 9 | using System.IO; 10 | 11 | namespace TinyHttpService.ActionResults 12 | { 13 | public class ViewResult : ActionResult 14 | { 15 | public string ViewPath { get; set; } 16 | 17 | public T Model { get; set; } 18 | 19 | public ViewResult(string viewPath, T model) 20 | { 21 | this.ViewPath = viewPath; 22 | this.Model = model; 23 | } 24 | 25 | public override async Task ExecuteAsync(HttpContext context) 26 | { 27 | var fullpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ViewPath.TrimStart('/', '\\')); 28 | if (File.Exists(fullpath)) 29 | { 30 | var razor = File.ReadAllText(fullpath, System.Text.Encoding.UTF8); 31 | string html = Razor.Parse(razor, this.Model); 32 | 33 | var response = context.Response; 34 | response.StatusCode = 200; 35 | response.ContentType = "text/html; charset=utf-8"; 36 | response.AddHeader("Content-Length", html.Length.ToString()); 37 | await response.WriteAsync(html); 38 | } 39 | else 40 | { 41 | await (new Http404NotFoundResult()).ExecuteAsync(context); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TinyHttpService/Utils/SubsenquenceFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.Utils 8 | { 9 | public static class SubsenquenceFinder 10 | { 11 | public static int Search(byte[] bytes, byte[] target) 12 | { 13 | if (bytes == null || target == null || 14 | bytes.Length == 0 || target.Length == 0) 15 | { 16 | return -1; 17 | } 18 | 19 | if (target.Length == 1) 20 | { 21 | for (int index = 0; index < bytes.Length; index++) 22 | { 23 | if (bytes[index] == target[0]) 24 | { 25 | return index; 26 | } 27 | } 28 | return -1; 29 | } 30 | 31 | int m = 0; 32 | int i = 0; 33 | int pos = m; 34 | 35 | while (pos <= bytes.Length - target.Length) 36 | { 37 | while (bytes[m] == target[i]) 38 | { 39 | m++; 40 | i++; 41 | if (i == target.Length) 42 | { 43 | return pos; 44 | } 45 | 46 | if (m == bytes.Length) 47 | { 48 | return -1; 49 | } 50 | } 51 | 52 | pos++; 53 | m = pos; 54 | i = 0; 55 | } 56 | 57 | return -1; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.Core; 8 | using TinyHttpService.Router; 9 | 10 | namespace TinyHttpService.Sample 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | RegisteRoute(); 17 | var service = DefaultTinyHttpServiceFactory.GetDefaultTinyHttpService(); 18 | service.Bind(5000); 19 | 20 | Console.ReadKey(); 21 | service.Close(); 22 | } 23 | 24 | private static void RegisteRoute() 25 | { 26 | var routes = RouteTable.Instance; 27 | 28 | routes.Get("/user/:id", (context) => 29 | { 30 | Console.WriteLine(context.Request.RouteData["id"]); 31 | 32 | return new JsonResult(new User() { UserName = "张浩" }); 33 | }); 34 | 35 | routes.Post("/user", (context) => 36 | { 37 | Console.WriteLine(context.Request.Body.ToString()); 38 | 39 | //如果你的程序是wpf程序,需要控制UI元素,你需要使用: 40 | //Element.Dispatcher.Invoke方法或者Elment.Dispatcher.BeginInvoke方法 41 | 42 | return new ContentResult("haha"); 43 | }); 44 | 45 | routes.Get("/user", (context) => 46 | { 47 | var user = new User(); 48 | user.UserName = "zhang"; 49 | return new ViewResult("/view/razor.cshtml", user); 50 | }); 51 | 52 | routes.Get("/download", (context) => 53 | { 54 | return new DownloadResult("/file/8.png"); 55 | }); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TinyHttpService.Test/Router/RouteHandlerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using TinyHttpService.Router; 4 | using TinyHttpService.ActionResults; 5 | using TinyHttpService.HttpData; 6 | 7 | namespace TinyHttpService.Test.Router 8 | { 9 | [TestClass] 10 | public class RouteHandlerTest 11 | { 12 | [TestInitialize] 13 | public void InitRouteTable() 14 | { 15 | RouteTable.Instance.Get("/user/:zhang/:id", (context) => 16 | { 17 | return new ContentResult("zhang"); 18 | }); 19 | 20 | RouteTable.Instance.Get("/index", (context) => 21 | { 22 | return new ViewResult("/index.cshtml", new User()); 23 | }); 24 | } 25 | 26 | [TestMethod] 27 | public void SelectExceptFuncAndCanParseRouteData() 28 | { 29 | HttpRequest request = new HttpRequest(); 30 | request.Uri = "/user/zhang/123456?aaa=2"; 31 | request.RequestMethod = "GET"; 32 | RouteHandler handler = new RouteHandler(); 33 | var func = handler.Handle(request); 34 | 35 | Assert.IsNotNull(func); 36 | var actionResult = func(null); 37 | Assert.AreEqual(actionResult.GetType(), typeof(ContentResult)); 38 | 39 | Assert.AreEqual(request.RouteData.RouteUri, "/user/:zhang/:id"); 40 | Assert.AreEqual(request.RouteData["zhang"], "zhang"); 41 | Assert.AreEqual(request.RouteData["id"], "123456"); 42 | } 43 | 44 | [TestCleanup] 45 | public void CleanRouteTable() 46 | { 47 | RouteTable.Instance.GetActions.Clear(); 48 | } 49 | 50 | public class User 51 | { 52 | public string UserName { get; set; } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/DownloadResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.ActionResults; 8 | using TinyHttpService.HttpData; 9 | 10 | namespace TinyHttpService.ActionResults 11 | { 12 | public class DownloadResult : ActionResult 13 | { 14 | public string FilePath { get; set; } 15 | 16 | public DownloadResult(string path) 17 | { 18 | this.FilePath = path; 19 | } 20 | 21 | public override async Task ExecuteAsync(HttpContext context) 22 | { 23 | var fullpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FilePath.TrimStart('/', '\\')); 24 | if (File.Exists(fullpath)) 25 | { 26 | var response = context.Response; 27 | var filename = Path.GetFileName(fullpath); 28 | var ext = Path.GetExtension(fullpath); 29 | 30 | response.StatusCode = 200; 31 | response.ContentType = Mime.Get(ext.ToLower()) ?? "application/octet-stream"; 32 | response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", filename)); 33 | 34 | var buffer = new byte[4096]; 35 | int readBytes = 0; 36 | using (var fs = new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.Read)) 37 | { 38 | response.AddHeader("Content-Length", fs.Length.ToString()); 39 | while ((readBytes = await fs.ReadAsync(buffer, 0, 4096)) > 0) 40 | { 41 | await response.WriteAsync(buffer); 42 | } 43 | } 44 | } 45 | else 46 | { 47 | await (new Http404NotFoundResult()).ExecuteAsync(context); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TinyHttpService/Core/HttpServiceHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TinyHttpService.ActionResults; 9 | using TinyHttpService.Core; 10 | using TinyHttpService.HttpData; 11 | using TinyHttpService.RequestParser; 12 | using TinyHttpService.Router; 13 | 14 | namespace TinyHttpService 15 | { 16 | public class HttpServiceHandler : IHttpServiceHandler 17 | { 18 | private IHttpRequestParser requestParser; 19 | private IRouteHandler routeHandler; 20 | 21 | public HttpServiceHandler(IHttpRequestParser requestParser, IRouteHandler routeHandler) 22 | { 23 | this.requestParser = requestParser; 24 | this.routeHandler = routeHandler; 25 | } 26 | 27 | public async Task ProcessRequestAsync(Stream stream) 28 | { 29 | HttpResponse response = new HttpResponse(stream); 30 | HttpRequest request = null; 31 | 32 | bool isParseSuccess; 33 | try 34 | { 35 | request = await requestParser.ParseAsync(stream); 36 | isParseSuccess = true; 37 | } 38 | catch (HttpRequestParseException e) 39 | { 40 | response.StatusCode = 400; 41 | isParseSuccess = false; 42 | } 43 | 44 | if (!isParseSuccess) 45 | { 46 | await response.WriteAsync("Bad Request"); 47 | } 48 | 49 | HttpContext context = new HttpContext 50 | { 51 | Request = request, 52 | Response = response 53 | }; 54 | 55 | var func = routeHandler.Handle(request); 56 | if (func != null) 57 | { 58 | ActionResult actionResult = func(context); 59 | await actionResult.ExecuteAsync(context); 60 | } 61 | else 62 | { 63 | await (new Http404NotFoundResult()).ExecuteAsync(context); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TinyHttpService/Core/HttpService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using TinyHttpService.Core; 11 | 12 | namespace TinyHttpService.Core 13 | { 14 | public class HttpService : IHttpService 15 | { 16 | private TcpListener listener; 17 | private IHttpServiceHandler httpServiceHandler; 18 | private bool active = true; 19 | 20 | public HttpService(IHttpServiceHandler handler) 21 | { 22 | this.httpServiceHandler = handler; 23 | } 24 | 25 | public virtual async void Bind(int port) 26 | { 27 | listener = new TcpListener(IPAddress.Any, port); 28 | listener.Start(); 29 | 30 | while (active) 31 | { 32 | using (var client = await listener.AcceptTcpClientAsync()) 33 | { 34 | while (client.Connected 35 | && client.Client.Poll(01, SelectMode.SelectRead) 36 | && client.Client.Poll(01, SelectMode.SelectWrite) 37 | && !client.Client.Poll(01, SelectMode.SelectError)) 38 | { 39 | if (client.GetStream().DataAvailable) 40 | { 41 | try 42 | { 43 | await httpServiceHandler.ProcessRequestAsync(client.GetStream()); 44 | } 45 | catch (IOException) 46 | { 47 | //when client interrupt connect, it would be catched. 48 | //ignore... 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | public virtual void Close() 57 | { 58 | Dispose(); 59 | } 60 | 61 | public virtual void Dispose() 62 | { 63 | active = false; 64 | 65 | if (listener != null) 66 | { 67 | listener.Stop(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TinyHttpService/ActionResults/StaticResourceResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.ActionResults; 8 | using TinyHttpService.HttpData; 9 | 10 | namespace TinyHttpService.ActionResults 11 | { 12 | public class StaticResourceResult : ActionResult 13 | { 14 | public string FilePath { get; set; } 15 | 16 | public StaticResourceResult(string path) 17 | { 18 | this.FilePath = path; 19 | } 20 | 21 | public override async Task ExecuteAsync(HttpContext context) 22 | { 23 | var fullpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FilePath.TrimStart('/', '\\')); 24 | var response = context.Response; 25 | var request = context.Request; 26 | 27 | if (File.Exists(fullpath)) 28 | { 29 | if (request.Header["If-Modified-Since"] != null) 30 | { 31 | DateTime time; 32 | if (DateTime.TryParse(request.Header["If-Modified-Since"], out time)) 33 | { 34 | if (time.Ticks >= File.GetLastWriteTimeUtc(fullpath).Ticks) 35 | { 36 | response.StatusCode = 304; 37 | await response.WriteAsync(string.Empty); 38 | return; 39 | } 40 | } 41 | } 42 | 43 | var ext = Path.GetExtension(fullpath); 44 | response.StatusCode = 200; 45 | response.ContentType = Mime.Get(ext) ?? "application/octet-stream"; 46 | response.AddHeader("Last-Modified", File.GetLastWriteTime(fullpath).ToUniversalTime().ToString("r")); 47 | 48 | var buffer = new byte[4096]; 49 | int readBytes = 0; 50 | using (var fs = new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.Read)) 51 | { 52 | response.AddHeader("Content-Length", fs.Length.ToString()); 53 | while ((readBytes = await fs.ReadAsync(buffer, 0, 4096)) > 0) 54 | { 55 | await response.WriteAsync(buffer); 56 | } 57 | } 58 | } 59 | else 60 | { 61 | await (new Http404NotFoundResult()).ExecuteAsync(context); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /TinyHttpService/Utils/StreamUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TinyHttpService.Utils 10 | { 11 | public static class StreamUtil 12 | { 13 | /// 14 | /// 从当前流的位置开始读取一行 15 | /// 16 | /// 17 | public static string ReadLine(this Stream stream, Encoding e) 18 | { 19 | var line = stream.ReadRawLine(e); 20 | if (line.Contains("\r\n")) 21 | { 22 | line = line.Substring(0, line.Length - 2); 23 | } 24 | else if (line.Contains('\n')) 25 | { 26 | line = line.Substring(0, line.Length - 1); 27 | } 28 | return line; 29 | } 30 | 31 | /// 32 | /// 从流中读取一行,并且包含换行符 33 | /// 34 | /// 35 | /// 36 | public static String ReadRawLine(this Stream stream, Encoding e) 37 | { 38 | var bytes = new List(); 39 | int value = -1; 40 | string line = string.Empty; 41 | 42 | NetworkStream networkStream = stream as NetworkStream; 43 | if (networkStream != null) 44 | { 45 | if (!networkStream.DataAvailable) 46 | { 47 | return string.Empty; 48 | } 49 | } 50 | 51 | while (stream.CanRead 52 | && (networkStream == null || networkStream.DataAvailable) 53 | && (value = stream.ReadByte()) != -1) 54 | { 55 | bytes.Add(Convert.ToByte(value)); 56 | 57 | if (value == '\n') 58 | { 59 | break; 60 | } 61 | } 62 | 63 | return e.GetString(bytes.ToArray()); 64 | } 65 | 66 | public static void Write(this Stream stream, string str) 67 | { 68 | if (stream.CanWrite) 69 | { 70 | byte[] bytes = Encoding.Default.GetBytes(str); 71 | stream.Write(bytes, 0, bytes.Length); 72 | stream.Flush(); 73 | return; 74 | } 75 | 76 | throw new InvalidOperationException("stream can't write"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /TinyHttpService.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyHttpService", "TinyHttpService\TinyHttpService.csproj", "{CA872E2D-4459-455A-9799-60596C57DE36}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyHttpService.Test", "TinyHttpService.Test\TinyHttpService.Test.csproj", "{04C7119F-7716-4A7A-AFCC-626F8D1BDF73}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D5DDA7AF-58E1-4B81-B792-E481F325C2A3}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5163B556-6ECC-43B3-A536-F228FFDA4614}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyHttpService.Sample", "TinyHttpService.Sample\TinyHttpService.Sample.csproj", "{F176872C-0B31-41D5-9F6E-EDB954040BF1}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {CA872E2D-4459-455A-9799-60596C57DE36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {CA872E2D-4459-455A-9799-60596C57DE36}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {CA872E2D-4459-455A-9799-60596C57DE36}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {CA872E2D-4459-455A-9799-60596C57DE36}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {F176872C-0B31-41D5-9F6E-EDB954040BF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {F176872C-0B31-41D5-9F6E-EDB954040BF1}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {F176872C-0B31-41D5-9F6E-EDB954040BF1}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {F176872C-0B31-41D5-9F6E-EDB954040BF1}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {CA872E2D-4459-455A-9799-60596C57DE36} = {D5DDA7AF-58E1-4B81-B792-E481F325C2A3} 40 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73} = {5163B556-6ECC-43B3-A536-F228FFDA4614} 41 | {F176872C-0B31-41D5-9F6E-EDB954040BF1} = {D5DDA7AF-58E1-4B81-B792-E481F325C2A3} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /TinyHttpService.Test/Utils/RebufferableStreamReaderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.IO; 4 | using System.Text; 5 | using TinyHttpService.Utils; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.Test.Utils 9 | { 10 | [TestClass] 11 | public class RebufferableStreamReaderTest 12 | { 13 | private static readonly string readerTestString = "ni hao wo shi zhanghao\r\nthis is test!"; 14 | private static readonly string lineBufferTestString = "hahaha\r\nhehe"; 15 | private static readonly string noLineBufferTestString = "this buffer no line"; 16 | 17 | [TestMethod] 18 | public async Task ReadLineWhenReaderNoBuffer() 19 | { 20 | using (var ms = new MemoryStream(Encoding.Default.GetBytes(readerTestString))) 21 | { 22 | RebufferableStreamReader reader = new RebufferableStreamReader(ms, Encoding.Default); 23 | string line = await reader.ReadLineAsync(); 24 | string nextLine = await reader.ReadLineAsync(); 25 | 26 | Assert.AreEqual(line, "ni hao wo shi zhanghao"); 27 | Assert.AreEqual(nextLine, "this is test!"); 28 | } 29 | } 30 | 31 | [TestMethod] 32 | public async Task ReadLineWhenLineInBuffer() 33 | { 34 | using (var ms = new MemoryStream(Encoding.Default.GetBytes(readerTestString))) 35 | { 36 | RebufferableStreamReader reader = new RebufferableStreamReader(ms, Encoding.Default); 37 | reader.Rebuffer(Encoding.Default.GetBytes(lineBufferTestString)); 38 | 39 | var line = await reader.ReadLineAsync(); 40 | var secondLine = await reader.ReadLineAsync(); 41 | var thirdLine = await reader.ReadLineAsync(); 42 | 43 | Assert.AreEqual(line, "hahaha"); 44 | Assert.AreEqual(secondLine, "hehe" + "ni hao wo shi zhanghao"); 45 | Assert.AreEqual(thirdLine, "this is test!"); 46 | 47 | reader.Rebuffer(Encoding.Default.GetBytes("hello world")); 48 | var fourLine = await reader.ReadLineAsync(); 49 | Assert.AreEqual(fourLine, "hello world"); 50 | } 51 | } 52 | 53 | [TestMethod] 54 | public async Task ReadLineWhenReaderHaveBufferAndNoLineInBuffer() 55 | { 56 | using (var ms = new MemoryStream(Encoding.Default.GetBytes(readerTestString))) 57 | { 58 | RebufferableStreamReader reader = new RebufferableStreamReader(ms, Encoding.Default); 59 | reader.Rebuffer(Encoding.Default.GetBytes(noLineBufferTestString)); 60 | 61 | var line = await reader.ReadLineAsync(); 62 | var secondLine = await reader.ReadLineAsync(); 63 | var thirdLine = await reader.ReadLineAsync(); 64 | 65 | Assert.AreEqual(line, noLineBufferTestString + "ni hao wo shi zhanghao"); 66 | Assert.AreEqual(secondLine, "this is test!"); 67 | Assert.IsTrue(string.IsNullOrEmpty(thirdLine)); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/HttpRequestParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using TinyHttpService.HttpData; 10 | using TinyHttpService.RequestParser.Commands; 11 | using TinyHttpService.Utils; 12 | 13 | namespace TinyHttpService.RequestParser 14 | { 15 | public class HttpRequestParser : IHttpRequestParser 16 | { 17 | public async Task ParseAsync(Stream stream) 18 | { 19 | var streamReader = new StreamReader(stream); 20 | string startLine = await streamReader.ReadLineAsync(); 21 | 22 | HttpRequest request = new HttpRequest(); 23 | var startLineRule = new Regex(@"^(GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT|OPTIONS) (.+) HTTP/1.1$"); 24 | 25 | if (startLineRule.IsMatch(startLine)) 26 | { 27 | var match = startLineRule.Match(startLine); 28 | request.RequestMethod = match.Groups[1].Value.Trim(); 29 | request.Uri = UrlHelper.UrlDecode(match.Groups[2].Value.Trim(), Encoding.UTF8); 30 | request.QueryString = UrlHelper.GetQueryString(request.Uri); 31 | } 32 | else 33 | { 34 | throw new HttpRequestParseException("http格式错误"); 35 | } 36 | 37 | request.Header = await GetHttpHeaderAsync(streamReader); 38 | 39 | Encoding encoding = GetBodyEncoding(request.Header["Content-Type"] ?? string.Empty); 40 | 41 | RequestBodyDataParseCommand command = 42 | BodyParseCommandFactory.GetBodyParseCommand(request.Header["Content-Type"]); 43 | HttpRequestBody body = await command.ExecuteAsync(stream, encoding); 44 | 45 | request.Body = body; 46 | return request; 47 | } 48 | 49 | private static async Task GetHttpHeaderAsync(StreamReader reader) 50 | { 51 | HttpHeader header = new HttpHeader(); 52 | string line; 53 | var headerPropertyRule = new Regex(@"(.+?):(.+)"); 54 | while ((line = await reader.ReadLineAsync()) != String.Empty) 55 | { 56 | if (headerPropertyRule.IsMatch(line)) 57 | { 58 | var match = headerPropertyRule.Match(line); 59 | header[match.Groups[1].Value.Trim()] = match.Groups[2].Value.Trim(); 60 | } 61 | else 62 | { 63 | throw new HttpRequestParseException("http头部格式错误"); 64 | } 65 | } 66 | 67 | return header; 68 | } 69 | 70 | private static Encoding GetBodyEncoding(string contentType) 71 | { 72 | var regex = new Regex(@"charset=([A-Za-z0-9\-]+)"); 73 | string charset = string.Empty; 74 | 75 | if (regex.IsMatch(contentType)) 76 | { 77 | var matcher = regex.Match(contentType); 78 | charset = matcher.Groups[1].Value; 79 | } 80 | Encoding encoding = charset == string.Empty ? Encoding.UTF8 : Encoding.GetEncoding(charset); 81 | return encoding; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TinyHttpService.Utils; 9 | 10 | namespace TinyHttpService.HttpData 11 | { 12 | public class HttpResponse 13 | { 14 | public Stream ResponseStream { get; private set; } 15 | 16 | public HttpHeader Header { get; set; } 17 | 18 | private int statusCode; 19 | private bool isHeaderWritten; 20 | 21 | public HttpResponse(Stream stream) 22 | { 23 | this.ResponseStream = stream; 24 | this.Header = new HttpHeader(); 25 | this.ContentType = "text/html"; 26 | this.statusCode = 200; 27 | this.isHeaderWritten = false; 28 | } 29 | 30 | public void AddHeader(string key, string value) 31 | { 32 | if (isHeaderWritten) 33 | { 34 | throw new InvalidOperationException("http头部已经发送,无法填加新的http头部"); 35 | } 36 | 37 | this.Header[key] = value; 38 | } 39 | 40 | public int StatusCode 41 | { 42 | get 43 | { 44 | return statusCode; 45 | } 46 | set 47 | { 48 | if (HttpStatusCodes.StatusCodes[value] == null) 49 | { 50 | throw new InvalidOperationException("无效的状态码: " + value.ToString()); 51 | } 52 | 53 | statusCode = value; 54 | } 55 | } 56 | 57 | private async Task WriteHeadAsync() 58 | { 59 | string head = string.Format("HTTP/1.1 {0} {1}\r\n{2}\r\n", 60 | StatusCode, HttpStatusCodes.StatusCodes[StatusCode], Header.ToString()); 61 | 62 | var headBytes = Encoding.UTF8.GetBytes(head); 63 | await ResponseStream.WriteAsync(Encoding.UTF8.GetBytes(head), 0, headBytes.Length); 64 | ResponseStream.Flush(); 65 | isHeaderWritten = true; 66 | } 67 | 68 | public async Task WriteAsync(byte[] bytes) 69 | { 70 | if (!isHeaderWritten) 71 | { 72 | await WriteHeadAsync(); 73 | } 74 | 75 | if (bytes != null) 76 | { 77 | await ResponseStream.WriteAsync(bytes, 0, bytes.Length); 78 | } 79 | } 80 | 81 | public async Task WriteAsync(string str) 82 | { 83 | if (!isHeaderWritten) 84 | { 85 | await WriteHeadAsync(); 86 | } 87 | 88 | if (!string.IsNullOrEmpty(str)) 89 | { 90 | var bytes = Encoding.UTF8.GetBytes(str); 91 | await ResponseStream.WriteAsync(bytes, 0, bytes.Length); 92 | } 93 | 94 | await ResponseStream.FlushAsync(); 95 | } 96 | 97 | private string contentType; 98 | 99 | public string ContentType 100 | { 101 | get 102 | { 103 | return contentType; 104 | } 105 | set 106 | { 107 | Header["Content-Type"] = value; 108 | this.contentType = value; 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TinyHttpService/Router/RouteTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TinyHttpService.ActionResults; 7 | using TinyHttpService.HttpData; 8 | 9 | namespace TinyHttpService.Router 10 | { 11 | public class RouteTable 12 | { 13 | private static RouteTable routeTable; 14 | 15 | public static RouteTable Instance 16 | { 17 | get 18 | { 19 | if (routeTable == null) 20 | { 21 | routeTable = new RouteTable(); 22 | } 23 | 24 | return routeTable; 25 | } 26 | } 27 | 28 | private RouteTable() 29 | { 30 | GetActions = new Dictionary>(); 31 | PostActions = new Dictionary>(); 32 | PutActions = new Dictionary>(); 33 | DeleteActions = new Dictionary>(); 34 | AllActions = new Dictionary>(); 35 | } 36 | 37 | public Dictionary> GetActions { get; private set; } 38 | 39 | public Dictionary> PostActions { get; private set; } 40 | 41 | public Dictionary> PutActions { get; private set; } 42 | 43 | public Dictionary> DeleteActions { get; private set; } 44 | 45 | public Dictionary> AllActions { get; private set; } 46 | 47 | public RouteTable All(string url, Func func) 48 | { 49 | AllActions[url] = func; 50 | return this; 51 | } 52 | 53 | public RouteTable Get(string url, Func func) 54 | { 55 | GetActions[url] = func; 56 | return this; 57 | } 58 | 59 | public RouteTable Post(string url, Func func) 60 | { 61 | PostActions[url] = func; 62 | return this; 63 | } 64 | 65 | public RouteTable Put(string url, Func func) 66 | { 67 | PutActions[url] = func; 68 | return this; 69 | } 70 | 71 | public RouteTable Delete(string url, Func func) 72 | { 73 | DeleteActions[url] = func; 74 | return this; 75 | } 76 | 77 | public Dictionary> this[string method] 78 | { 79 | get 80 | { 81 | method = method ?? string.Empty; 82 | switch (method.ToUpper()) 83 | { 84 | case "GET": 85 | return GetActions; 86 | case "POST": 87 | return PostActions; 88 | case "PUT": 89 | return PutActions; 90 | case "DELETE": 91 | return DeleteActions; 92 | default: 93 | return new Dictionary>(); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /TinyHttpService.Sample/TinyHttpService.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F176872C-0B31-41D5-9F6E-EDB954040BF1} 8 | Exe 9 | Properties 10 | TinyHttpService.Sample 11 | TinyHttpService.Sample 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Code 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Always 54 | 55 | 56 | Always 57 | 58 | 59 | 60 | Always 61 | 62 | 63 | 64 | 65 | {ca872e2d-4459-455a-9799-60596c57de36} 66 | TinyHttpService 67 | 68 | 69 | 70 | 71 | Always 72 | 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /TinyHttpService/HttpData/HttpStatusCodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.HttpData 8 | { 9 | public static class HttpStatusCodes 10 | { 11 | public readonly static Dictionary StatusCodes; 12 | 13 | static HttpStatusCodes() 14 | { 15 | StatusCodes = new Dictionary 16 | { 17 | { 100, "Continue" }, 18 | { 101, "Switching Protocols" }, 19 | { 102, "Processing" }, // RFC 2518, obsoleted by RFC 4918 20 | { 200, "OK" }, 21 | { 201, "Created" }, 22 | { 202, "Accepted" }, 23 | { 203, "Non-Authoritative Information" }, 24 | { 204, "No Content" }, 25 | { 205, "Reset Content" }, 26 | { 206, "Partial Content" }, 27 | { 207, "Multi-Status" }, // RFC 4918 28 | { 300, "Multiple Choices" }, 29 | { 301, "Moved Permanently" }, 30 | { 302, "Moved Temporarily" }, 31 | { 303, "See Other" }, 32 | { 304, "Not Modified" }, 33 | { 305, "Use Proxy" }, 34 | { 307, "Temporary Redirect" }, 35 | { 400, "Bad Request" }, 36 | { 401, "Unauthorized" }, 37 | { 402, "Payment Required" }, 38 | { 403, "Forbidden" }, 39 | { 404, "Not Found" }, 40 | { 405, "Method Not Allowed" }, 41 | { 406, "Not Acceptable" }, 42 | { 407, "Proxy Authentication Required" }, 43 | { 408, "Request Time-out" }, 44 | { 409, "Conflict" }, 45 | { 410, "Gone" }, 46 | { 411, "Length Required" }, 47 | { 412, "Precondition Failed" }, 48 | { 413, "Request Entity Too Large" }, 49 | { 414, "Request-URI Too Large" }, 50 | { 415, "Unsupported Media Type" }, 51 | { 416, "Requested Range Not Satisfiable" }, 52 | { 417, "Expectation Failed" }, 53 | { 418, "I\'m a teapot" }, // RFC 2324 54 | { 422, "Unprocessable Entity" }, // RFC 4918 55 | { 423, "Locked" }, // RFC 4918 56 | { 424, "Failed Dependency" }, // RFC 4918 57 | { 425, "Unordered Collection" }, // RFC 4918 58 | { 426, "Upgrade Required" }, // RFC 2817 59 | { 428, "Precondition Required" }, // RFC 6585 60 | { 429, "Too Many Requests" }, // RFC 6585 61 | { 431, "Request Header Fields Too Large" }, // RFC 6585 62 | { 500, "Internal Server Error" }, 63 | { 501, "Not Implemented" }, 64 | { 502, "Bad Gateway" }, 65 | { 503, "Service Unavailable" }, 66 | { 504, "Gateway Time-out" }, 67 | { 505, "HTTP Version Not Supported" }, 68 | { 506, "Variant Also Negotiates" }, // RFC 2295 69 | { 507, "Insufficient Storage" }, // RFC 4918 70 | { 509, "Bandwidth Limit Exceeded" }, 71 | { 510, "Not Extended" }, // RFC 2774 72 | { 511, "Network Authentication Required" } // RFC 6585 73 | }; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /TinyHttpService/Router/RouteHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using TinyHttpService.ActionResults; 9 | using TinyHttpService.HttpData; 10 | using TinyHttpService.Router; 11 | using TinyHttpService.Utils; 12 | 13 | namespace TinyHttpService.Router 14 | { 15 | public class RouteHandler : IRouteHandler 16 | { 17 | public RouteTable Routes 18 | { 19 | get 20 | { 21 | return RouteTable.Instance; 22 | } 23 | } 24 | 25 | private static Func staticFileFuncCache; 26 | 27 | static RouteHandler() 28 | { 29 | staticFileFuncCache = new Func((context) => 30 | { 31 | string requestFile = string.Empty; 32 | if (context.Request.Uri == "/favicon.ico") 33 | { 34 | requestFile = context.Request.Uri; 35 | } 36 | else 37 | { 38 | requestFile = TinyHttpServiceConfig.StaticFilePath + context.Request.Uri; 39 | } 40 | 41 | return new StaticResourceResult(requestFile); 42 | }); 43 | } 44 | 45 | public Func Handle(HttpRequest request) 46 | { 47 | if (IsRequestStaticFile(request.Uri)) 48 | { 49 | return staticFileFuncCache; 50 | } 51 | 52 | var func = SelectAndParseRouteData(request, Routes[request.RequestMethod]); 53 | 54 | if (func != null) 55 | { 56 | return func; 57 | } 58 | 59 | return SelectAndParseRouteData(request, Routes.AllActions); 60 | } 61 | 62 | private bool IsRequestStaticFile(string url) 63 | { 64 | Regex rule = new Regex(@"\.(htm|html|js|css|ico|jpg|jpeg|png|bmp|txt|zip|mp4|mp3|avi|wma|wmv)$"); 65 | return "/favicon.ico" == url.ToLower() || rule.IsMatch(url); 66 | } 67 | 68 | private Func SelectAndParseRouteData( 69 | HttpRequest request, 70 | Dictionary> routes) 71 | { 72 | string urlParam = request.Uri.Split('?')[0]; 73 | string[] urlArray = urlParam.Split('/'); 74 | request.RouteData = new RouteData(); 75 | 76 | foreach (var kv in routes) 77 | { 78 | string[] routeArray = kv.Key.Split('/'); 79 | bool isMatch = true; 80 | if (routeArray.Length == urlArray.Length) 81 | { 82 | for (int i = 0; i < routeArray.Length; i++) 83 | { 84 | if (routeArray[i].StartsWith(":")) 85 | { 86 | request.RouteData[routeArray[i].TrimStart(':')] = urlArray[i]; 87 | } 88 | else 89 | { 90 | if (routeArray[i] != urlArray[i]) 91 | { 92 | request.RouteData.DataTokens.Clear(); 93 | isMatch = false; 94 | continue; 95 | } 96 | } 97 | } 98 | 99 | if (isMatch) 100 | { 101 | request.RouteData.RouteUri = kv.Key; 102 | return Routes[request.RequestMethod][kv.Key]; 103 | } 104 | } 105 | } 106 | 107 | return null; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /TinyHttpService/Utils/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TinyHttpService.Utils 8 | { 9 | public class UrlHelper 10 | { 11 | public static string UrlDecode(string s, Encoding e = null) 12 | { 13 | if (s == null) 14 | { 15 | return null; 16 | } 17 | 18 | if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1) 19 | { 20 | return s; 21 | } 22 | 23 | if (e == null) 24 | { 25 | e = Encoding.UTF8; 26 | } 27 | 28 | long len = s.Length; 29 | var bytes = new List(); 30 | int xchar; 31 | char ch; 32 | 33 | for (int i = 0; i < len; i++) 34 | { 35 | ch = s[i]; 36 | if (ch == '%' && i + 2 < len && s[i + 1] != '%') 37 | { 38 | if (s[i + 1] == 'u' && i + 5 < len) 39 | { 40 | // unicode hex 41 | xchar = GetChar(s, i + 2, 4); 42 | if (xchar != -1) 43 | { 44 | WriteCharBytes(bytes, (char)xchar, e); 45 | i += 5; 46 | } 47 | else 48 | { 49 | WriteCharBytes(bytes, '%', e); 50 | } 51 | } 52 | else if ((xchar = GetChar(s, i + 1, 2)) != -1) 53 | { 54 | WriteCharBytes(bytes, (char)xchar, e); 55 | i += 2; 56 | } 57 | else 58 | { 59 | WriteCharBytes(bytes, '%', e); 60 | } 61 | continue; 62 | } 63 | 64 | if (ch == '+') 65 | WriteCharBytes(bytes, ' ', e); 66 | else 67 | WriteCharBytes(bytes, ch, e); 68 | } 69 | 70 | byte[] buf = bytes.ToArray(); 71 | bytes = null; 72 | return e.GetString(buf); 73 | 74 | } 75 | 76 | private static void WriteCharBytes(IList buf, char ch, Encoding e) 77 | { 78 | if (ch > 255) 79 | { 80 | foreach (byte b in e.GetBytes(new char[] { ch })) 81 | { 82 | buf.Add(b); 83 | } 84 | } 85 | else 86 | { 87 | buf.Add((byte)ch); 88 | } 89 | } 90 | 91 | private static int GetChar(string str, int offset, int length) 92 | { 93 | int val = 0; 94 | int end = length + offset; 95 | for (int i = offset; i < end; i++) 96 | { 97 | char c = str[i]; 98 | if (c > 127) 99 | return -1; 100 | 101 | int current = GetInt((byte)c); 102 | if (current == -1) 103 | return -1; 104 | val = (val << 4) + current; 105 | } 106 | 107 | return val; 108 | } 109 | 110 | private static int GetInt(byte b) 111 | { 112 | char c = (char)b; 113 | if (c >= '0' && c <= '9') 114 | return c - '0'; 115 | 116 | if (c >= 'a' && c <= 'f') 117 | return c - 'a' + 10; 118 | 119 | if (c >= 'A' && c <= 'F') 120 | return c - 'A' + 10; 121 | 122 | return -1; 123 | } 124 | 125 | public static Dictionary GetQueryString(string url) 126 | { 127 | string[] strArray = url.Split('?'); 128 | if (strArray.Length < 2) 129 | { 130 | return new Dictionary(); 131 | } 132 | 133 | var queryString = strArray[1] 134 | .Split('&') 135 | .Select(s => s.Split('=')) 136 | .ToDictionary(x => x[0], x => x[1]); 137 | return queryString; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /TinyHttpService.Test/RequestParser/MultiPartFormDataParserTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using TinyHttpService.RequestParser; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.Test.RequestParser 9 | { 10 | [TestClass] 11 | public class MultiPartFormDataParserTest 12 | { 13 | private static readonly string multiParamsAndFilesTestData = "--boundry\r\n" + 14 | @"Content-Disposition: form-data; name=""text""" + 15 | "\r\n\r\ntextdata" + 16 | "\r\n--boundry\r\n" + 17 | @"Content-Disposition: form-data; name=""file""; filename=""first.txt"";" + 18 | "\r\nContent-Type: text/plain\r\n\r\n" + 19 | "aaaaaaa" + 20 | "\r\n--boundry\r\n" + 21 | @"Content-Disposition: form-data; name=""newfile""; filename=""second.txt"";" + 22 | "\r\nContent-Type: text/plain\r\n\r\n" + 23 | "bbbbbbb" + 24 | "\r\n--boundry\r\n" + 25 | @"Content-Disposition: form-data; name=""username""" + 26 | "\r\n\r\nzhang" + 27 | "\r\n--boundry--"; 28 | 29 | private static readonly string multiParamsTestData = "--boundry\r\n" + 30 | @"Content-Disposition: form-data; name=""text""" + 31 | "\r\n\r\ntextdata" + 32 | "\r\n--boundry\r\n" + 33 | @"Content-Disposition: form-data; name=""username""" + 34 | "\r\n\r\nzhang" + 35 | "\r\n--boundry--"; 36 | 37 | private static readonly string multiFilesTestData = 38 | "--boundry\r\n" + 39 | @"Content-Disposition: form-data; name=""file""; filename=""first.txt"";" + 40 | "\r\nContent-Type: text/plain\r\n\r\n" + 41 | "aaaaaaa" + 42 | "\r\n--boundry\r\n" + 43 | @"Content-Disposition: form-data; name=""newfile""; filename=""second.txt"";" + 44 | "\r\nContent-Type: text/plain\r\n\r\n" + 45 | "bbbbbbb" + 46 | "\r\n--boundry--"; 47 | 48 | [TestMethod] 49 | public async Task CanParseMultiParamsData() 50 | { 51 | using (MemoryStream ms = new MemoryStream(Encoding.Default.GetBytes(multiParamsTestData))) 52 | { 53 | MultiPartFormDataParser parser = new MultiPartFormDataParser(ms, Encoding.Default); 54 | await parser.ParseAsync(); 55 | Assert.AreEqual(parser.Parameters["text"], "textdata"); 56 | Assert.AreEqual(parser.Parameters["username"], "zhang"); 57 | } 58 | } 59 | 60 | [TestMethod] 61 | public async Task CanParseMultiFilesData() 62 | { 63 | using (MemoryStream ms = new MemoryStream(Encoding.Default.GetBytes(multiFilesTestData))) 64 | { 65 | MultiPartFormDataParser parser = new MultiPartFormDataParser(ms, Encoding.Default); 66 | await parser.ParseAsync(); 67 | Assert.AreEqual(parser.Files.Count, 2); 68 | Assert.AreEqual(parser.Files[0].Filename, "first.txt"); 69 | Assert.AreEqual(parser.Files[0].Name, "file"); 70 | Assert.AreEqual(parser.Files[1].Filename, "second.txt"); 71 | Assert.AreEqual(parser.Files[1].Name, "newfile"); 72 | 73 | using (var sw1 = new StreamReader(parser.Files[0].Data)) 74 | using (var sw2 = new StreamReader(parser.Files[1].Data)) 75 | { 76 | string file1Str = sw1.ReadToEnd(); 77 | string file2Str = sw2.ReadToEnd(); 78 | 79 | Assert.AreEqual(file1Str, "aaaaaaa"); 80 | Assert.AreEqual(file2Str, "bbbbbbb"); 81 | } 82 | } 83 | } 84 | 85 | [TestMethod] 86 | public async Task CanParseMultiParamsAndFilesData() 87 | { 88 | using (MemoryStream ms = new MemoryStream(Encoding.Default.GetBytes(multiParamsAndFilesTestData))) 89 | { 90 | MultiPartFormDataParser parser = new MultiPartFormDataParser(ms, Encoding.Default); 91 | await parser.ParseAsync(); 92 | Assert.AreEqual(parser.Parameters["text"], "textdata"); 93 | Assert.AreEqual(parser.Parameters["username"], "zhang"); 94 | 95 | Assert.AreEqual(parser.Files.Count, 2); 96 | Assert.AreEqual(parser.Files[0].Filename, "first.txt"); 97 | Assert.AreEqual(parser.Files[0].Name, "file"); 98 | Assert.AreEqual(parser.Files[1].Filename, "second.txt"); 99 | Assert.AreEqual(parser.Files[1].Name, "newfile"); 100 | 101 | using (var sw1 = new StreamReader(parser.Files[0].Data)) 102 | using (var sw2 = new StreamReader(parser.Files[1].Data)) 103 | { 104 | string file1Str = sw1.ReadToEnd(); 105 | string file2Str = sw2.ReadToEnd(); 106 | 107 | Assert.AreEqual(file1Str, "aaaaaaa"); 108 | Assert.AreEqual(file2Str, "bbbbbbb"); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /TinyHttpService.Test/TinyHttpService.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {04C7119F-7716-4A7A-AFCC-626F8D1BDF73} 7 | Library 8 | Properties 9 | TinyHttpService.Test 10 | TinyHttpService.Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | False 40 | ..\packages\ServiceStack.Text.4.0.21\lib\net40\ServiceStack.Text.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Code 65 | 66 | 67 | 68 | 69 | {ca872e2d-4459-455a-9799-60596c57de36} 70 | TinyHttpService 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | False 81 | 82 | 83 | False 84 | 85 | 86 | False 87 | 88 | 89 | False 90 | 91 | 92 | 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /TinyHttpService/TinyHttpService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {CA872E2D-4459-455A-9799-60596C57DE36} 8 | Library 9 | Properties 10 | TinyHttpService 11 | TinyHttpService 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | False 35 | ..\packages\RazorEngine.3.4.1\lib\net45\RazorEngine.dll 36 | 37 | 38 | ..\packages\ServiceStack.Text.4.0.7\lib\net40\ServiceStack.Text.dll 39 | 40 | 41 | 42 | 43 | False 44 | ..\packages\Microsoft.AspNet.Razor.3.0.0\lib\net45\System.Web.Razor.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Code 56 | 57 | 58 | 59 | 60 | 61 | Code 62 | 63 | 64 | Code 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Code 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Code 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | -------------------------------------------------------------------------------- /TinyHttpService/Utils/RebufferableStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TinyHttpService.Utils 9 | { 10 | public class RebufferableStreamReader : IDisposable 11 | { 12 | private byte[] buffer; 13 | private Stream stream; 14 | private Encoding encoding; 15 | 16 | public RebufferableStreamReader(Stream stream, Encoding e) 17 | { 18 | if (stream == null) 19 | { 20 | throw new ArgumentNullException("stream"); 21 | } 22 | 23 | this.encoding = e ?? Encoding.UTF8; 24 | this.stream = stream; 25 | } 26 | 27 | public void Rebuffer(byte[] bytes, bool bufferEnd = false) 28 | { 29 | if (bytes == null) 30 | { 31 | throw new ArgumentNullException("bytes"); 32 | } 33 | 34 | if (buffer == null) 35 | { 36 | buffer = bytes; 37 | } 38 | else 39 | { 40 | if (!bufferEnd) 41 | { 42 | buffer = bytes.Concat(buffer).ToArray(); 43 | } 44 | else 45 | { 46 | buffer = buffer.Concat(bytes).ToArray(); 47 | } 48 | } 49 | } 50 | 51 | public async Task ReadLineAsync() 52 | { 53 | string line; 54 | int newLineIndex; 55 | buffer = buffer ?? new byte[0]; 56 | 57 | if (TryGetLine(buffer, buffer.Length, out line, out newLineIndex)) 58 | { 59 | byte[] newBuffer = new byte[buffer.Length - newLineIndex]; 60 | Array.Copy(buffer, newLineIndex, newBuffer, 0, newBuffer.Length); 61 | buffer = newBuffer; 62 | 63 | return line; 64 | } 65 | 66 | byte[] readBytes = new byte[4096]; 67 | int readCount = 0; 68 | do 69 | { 70 | readCount = await stream.ReadAsync(readBytes, 0, readBytes.Length); 71 | 72 | if (TryGetLine(readBytes, readCount, out line, out newLineIndex)) 73 | { 74 | line = encoding.GetString(buffer) + line; 75 | 76 | buffer = new byte[readCount - newLineIndex]; 77 | Array.Copy(readBytes, newLineIndex, buffer, 0, buffer.Length); 78 | 79 | return line; 80 | } 81 | 82 | if (readCount > 0) 83 | { 84 | buffer = buffer.Concat(readBytes).ToArray(); 85 | } 86 | } 87 | while (readCount > 0); 88 | 89 | if (buffer.Length == 0) 90 | { 91 | //end of stream 92 | return null; 93 | } 94 | 95 | line = encoding.GetString(buffer); 96 | buffer = null; 97 | return line; 98 | } 99 | 100 | public async Task ReadAsync(byte[] bytes, int offset, int length) 101 | { 102 | if (bytes == null) 103 | { 104 | throw new ArgumentNullException("bytes"); 105 | } 106 | 107 | if (offset < 0) 108 | { 109 | throw new ArgumentOutOfRangeException("offset必须大于0"); 110 | } 111 | 112 | if (offset + length > bytes.Length) 113 | { 114 | throw new ArgumentOutOfRangeException("bytes长度必须大于offset与length之和"); 115 | } 116 | 117 | if (buffer != null) 118 | { 119 | if (buffer.Length > length) 120 | { 121 | Buffer.BlockCopy(buffer, 0, bytes, offset, length); 122 | var newBuffer = new byte[buffer.Length - bytes.Length]; 123 | Buffer.BlockCopy(buffer, bytes.Length, newBuffer, 0, newBuffer.Length); 124 | buffer = newBuffer; 125 | return length; 126 | } 127 | else if (buffer.Length < length) 128 | { 129 | int readCount = await stream.ReadAsync(bytes, buffer.Length, length - buffer.Length); 130 | Buffer.BlockCopy(buffer, 0, bytes, offset, buffer.Length); 131 | readCount += buffer.Length; 132 | buffer = null; 133 | return readCount; 134 | } 135 | else 136 | { 137 | Buffer.BlockCopy(buffer, 0, bytes, offset, length); 138 | buffer = null; 139 | return length; 140 | } 141 | } 142 | else 143 | { 144 | return await stream.ReadAsync(bytes, offset, length); 145 | } 146 | } 147 | 148 | public void Dispose() 149 | { 150 | stream.Close(); 151 | } 152 | 153 | private bool TryGetLine(byte[] bytes, int count, out string line, out int newLineIndex) 154 | { 155 | for (int i = 0; i < count; i++) 156 | { 157 | if (bytes[i] == '\n') 158 | { 159 | int lineBytesCount = i; 160 | if (i > 0 && bytes[i - 1] == '\r') 161 | { 162 | lineBytesCount -= 1; 163 | } 164 | 165 | byte[] lineBytes = new byte[lineBytesCount]; 166 | byte[] newBuffer = new byte[count - i - 1]; 167 | Array.Copy(bytes, lineBytes, lineBytesCount); 168 | 169 | line = encoding.GetString(lineBytes); 170 | newLineIndex = i + 1; 171 | return true; 172 | } 173 | } 174 | 175 | line = string.Empty; 176 | newLineIndex = -1; 177 | return false; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /TinyHttpService/RequestParser/MultiPartFormDataParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TinyHttpService.HttpData; 8 | using TinyHttpService.Utils; 9 | 10 | namespace TinyHttpService.RequestParser 11 | { 12 | public class MultiPartFormDataParser 13 | { 14 | const int DEFAULT_BUFFER_LENGTH = 4096; 15 | 16 | private string boundary; 17 | private byte[] boundaryBytes; 18 | private string endBoundary; 19 | private byte[] endBoundaryBytes; 20 | private readonly Encoding encoding; 21 | 22 | private bool readEndBoundary = false; 23 | private RebufferableStreamReader reader; 24 | 25 | public int BinaryBufferSize { get; set; } 26 | public List Files { get; set; } 27 | public Dictionary Parameters { get; set; } 28 | 29 | public MultiPartFormDataParser(Stream stream, Encoding e, string boundaryKey = null, 30 | int binaryBufferSize = DEFAULT_BUFFER_LENGTH) 31 | { 32 | encoding = e ?? Encoding.UTF8; 33 | reader = new RebufferableStreamReader(stream, encoding); 34 | 35 | BinaryBufferSize = binaryBufferSize; 36 | Files = new List(); 37 | Parameters = new Dictionary(); 38 | } 39 | 40 | private async Task DetectBoundaryKey(RebufferableStreamReader reader) 41 | { 42 | var line = await reader.ReadLineAsync(); 43 | var boundary = string.Concat(line.Skip(2)); 44 | reader.Rebuffer(encoding.GetBytes("--" + boundary + "\r\n")); 45 | return boundary; 46 | } 47 | 48 | public async Task ParseAsync(string boundaryKey = null) 49 | { 50 | if (string.IsNullOrEmpty(boundaryKey)) 51 | { 52 | boundaryKey = await DetectBoundaryKey(reader); 53 | } 54 | 55 | boundary = string.Format("--{0}", boundaryKey); 56 | endBoundary = string.Format("--{0}--", boundaryKey); 57 | boundaryBytes = encoding.GetBytes(boundary); 58 | endBoundaryBytes = encoding.GetBytes(endBoundary); 59 | 60 | string line = await reader.ReadLineAsync(); 61 | while (!this.readEndBoundary) 62 | { 63 | string partHeader = string.Empty; 64 | while ((line = await reader.ReadLineAsync()) != string.Empty) 65 | { 66 | if (line != boundary) 67 | { 68 | partHeader += line; 69 | } 70 | } 71 | 72 | Dictionary partHeaders; 73 | try 74 | { 75 | partHeaders = partHeader.Replace("\r\n", ";").Split(';') 76 | .Select(x => x.Split(new[] { ':', '=' })) 77 | .ToDictionary( 78 | x => x[0].Trim().Replace("\"", string.Empty).ToLower(), 79 | x => x[1].Trim().Replace("\"", string.Empty)); 80 | } 81 | catch 82 | { 83 | throw new HttpRequestParseException("http报文实体格式错误"); 84 | } 85 | 86 | if (!partHeaders.ContainsKey("filename")) 87 | { 88 | await ParseParameterPartAsync(reader, partHeaders); 89 | } 90 | else 91 | { 92 | await ParseFilePartAsync(reader, partHeaders); 93 | } 94 | 95 | } 96 | } 97 | 98 | private async Task ParseParameterPartAsync(RebufferableStreamReader reader, Dictionary partHeaders) 99 | { 100 | StringBuilder value = new StringBuilder(); 101 | string line = await reader.ReadLineAsync(); 102 | while (line != endBoundary && line != boundary) 103 | { 104 | value.Append(line); 105 | line = await reader.ReadLineAsync(); 106 | } 107 | 108 | if (line == endBoundary) 109 | { 110 | this.readEndBoundary = true; 111 | } 112 | Parameters[partHeaders["name"]] = value.ToString(); 113 | } 114 | 115 | private async Task ParseFilePartAsync(RebufferableStreamReader reader, Dictionary partHeaders) 116 | { 117 | //先使用内存存储吧,遇到大文件肯定是不行的 118 | MemoryStream ms = new MemoryStream(); 119 | var curBuffer = new byte[this.BinaryBufferSize]; 120 | var prevBuffer = new byte[this.BinaryBufferSize]; 121 | 122 | int curLength = 0; 123 | int prevLength = await reader.ReadAsync(prevBuffer, 0, prevBuffer.Length); 124 | int endPos = -1; 125 | 126 | do 127 | { 128 | curLength = await reader.ReadAsync(curBuffer, 0, curBuffer.Length); 129 | var fullBuffer = new Byte[prevLength + curLength]; 130 | Buffer.BlockCopy(prevBuffer, 0, fullBuffer, 0, prevLength); 131 | Buffer.BlockCopy(curBuffer, 0, fullBuffer, prevLength, curLength); 132 | 133 | int endBoundaryPos = SubsenquenceFinder.Search(fullBuffer, endBoundaryBytes); 134 | int boundaryPos = SubsenquenceFinder.Search(fullBuffer, boundaryBytes); 135 | 136 | if (endBoundaryPos >= 0 && boundaryPos >= 0) 137 | { 138 | if (boundaryPos < endBoundaryPos) 139 | { 140 | endPos = boundaryPos; 141 | } 142 | else 143 | { 144 | endPos = endBoundaryPos; 145 | this.readEndBoundary = true; 146 | } 147 | } 148 | else if (boundaryPos >= 0 && endBoundaryPos < 0) 149 | { 150 | endPos = boundaryPos; 151 | } 152 | else if (endBoundaryPos >= 0 && boundaryPos < 0) 153 | { 154 | endPos = endBoundaryPos; 155 | this.readEndBoundary = true; 156 | } 157 | 158 | if (endPos != -1) 159 | { 160 | //这里endPos减2的原因是去除CRLF 161 | ms.Write(fullBuffer, 0, endPos - 2); 162 | var rebuffer = new byte[fullBuffer.Length - endPos]; 163 | Buffer.BlockCopy(fullBuffer, endPos, rebuffer, 0, rebuffer.Length); 164 | reader.Rebuffer(rebuffer); 165 | ms.Flush(); 166 | ms.Position = 0; 167 | FilePart filePart = new FilePart() 168 | { 169 | Filename = partHeaders["filename"], 170 | Name = partHeaders["name"], 171 | Data = ms 172 | }; 173 | 174 | this.Files.Add(filePart); 175 | return; 176 | } 177 | 178 | ms.Write(prevBuffer, 0, prevLength); 179 | ms.Flush(); 180 | prevBuffer = curBuffer; 181 | prevLength = curLength; 182 | } 183 | while (prevLength > 0); 184 | 185 | if (endPos == -1) 186 | { 187 | ms.Close(); 188 | throw new HttpRequestParseException("http报文实体格式错误"); 189 | } 190 | } 191 | } 192 | } 193 | --------------------------------------------------------------------------------