├── 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 |
--------------------------------------------------------------------------------