《C#实现一个最简单的HTTP服务器》
在.NET生态中,HTTP服务器的开发通常依赖ASP.NET Core框架,但若需要快速实现一个轻量级的HTTP服务器(例如用于测试、工具开发或嵌入式场景),可以通过C#的`HttpListener`类直接操作底层HTTP协议。本文将通过一个完整的示例,演示如何用最少的代码构建一个可响应GET/POST请求的HTTP服务器,并深入解析其工作原理。
一、HttpListener基础
`HttpListener`是.NET Framework和.NET Core中提供的底层HTTP监听器,位于`System.Net`命名空间。它允许开发者直接监听指定URL前缀(如`http://localhost:8080/`)的HTTP请求,无需配置IIS或Kestrel等完整Web服务器。
核心步骤:
- 创建`HttpListener`实例并配置监听前缀
- 启动监听(`Start()`方法)
- 进入异步等待循环(`GetContextAsync()`)
- 处理请求上下文(`HttpListenerContext`)
- 返回响应(`HttpListenerResponse`)
二、完整代码实现
以下是一个支持GET/POST请求的最小化HTTP服务器实现,包含HTML表单处理和JSON响应:
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
class SimpleHttpServer
{
private readonly HttpListener _listener = new HttpListener();
private readonly string _urlPrefix;
public SimpleHttpServer(string urlPrefix = "http://localhost:8080/")
{
_urlPrefix = urlPrefix;
}
public async Task StartAsync()
{
_listener.Prefixes.Add(_urlPrefix);
_listener.Start();
Console.WriteLine($"Server started at {_urlPrefix}");
try
{
while (true)
{
var context = await _listener.GetContextAsync();
_ = HandleRequestAsync(context); // 非阻塞处理
}
}
catch (HttpListenerException ex)
{
Console.WriteLine($"Listener error: {ex.Message}");
}
finally
{
_listener.Stop();
}
}
private async Task HandleRequestAsync(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
try
{
// 根据请求方法处理
if (request.HttpMethod == "GET")
{
await HandleGetRequest(response);
}
else if (request.HttpMethod == "POST")
{
await HandlePostRequest(request, response);
}
else
{
SendErrorResponse(response, HttpStatusCode.MethodNotAllowed, "Method not allowed");
}
}
catch (Exception ex)
{
SendErrorResponse(response, HttpStatusCode.InternalServerError, ex.Message);
}
finally
{
response.Close();
}
}
private async Task HandleGetRequest(HttpListenerResponse response)
{
var html = @"
Simple HTTP Server
Welcome to Simple HTTP Server
";
var buffer = Encoding.UTF8.GetBytes(html);
response.ContentType = "text/html";
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
private async Task HandlePostRequest(HttpListenerRequest request, HttpListenerResponse response)
{
// 读取POST数据
string postData;
using (var reader = new System.IO.StreamReader(request.InputStream, request.ContentEncoding))
{
postData = await reader.ReadToEndAsync();
}
// 简单解析表单数据(实际应用中应使用更健壮的解析器)
var message = "No message received";
if (!string.IsNullOrEmpty(postData))
{
var pairs = postData.Split('&');
foreach (var pair in pairs)
{
var kv = pair.Split('=');
if (kv.Length == 2 && kv[0] == "message")
{
message = System.Web.HttpUtility.UrlDecode(kv[1]);
break;
}
}
}
// 返回JSON响应
var jsonResponse = $"{{\"status\":\"success\",\"message\":\"{message}\"}}";
var buffer = Encoding.UTF8.GetBytes(jsonResponse);
response.ContentType = "application/json";
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
private void SendErrorResponse(HttpListenerResponse response, HttpStatusCode statusCode, string message)
{
var errorResponse = $"{{\"error\":\"{message}\",\"code\":{(int)statusCode}}}";
var buffer = Encoding.UTF8.GetBytes(errorResponse);
response.StatusCode = (int)statusCode;
response.ContentType = "application/json";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
public void Stop()
{
_listener.Stop();
_listener.Close();
}
}
// 使用示例
class Program
{
static async Task Main(string[] args)
{
var server = new SimpleHttpServer();
var startTask = server.StartAsync();
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
server.Stop();
await startTask;
}
}
三、代码解析
1. 监听配置
`HttpListener`通过`Prefixes`集合配置监听地址,支持:
- `http://+:8080/` - 监听所有网络接口的8080端口
- `http://localhost/` - 仅本地回环地址
- `http://192.168.1.100:80/` - 指定IP和端口
注意:在非管理员权限下运行可能需要配置URL保留(通过`netsh http add urlacl`命令)。
2. 请求处理流程
`GetContextAsync()`会阻塞直到收到请求,返回的`HttpListenerContext`包含:
- `Request`对象:包含方法、URL、头部、查询字符串、输入流等
- `Response`对象:用于设置状态码、头部、输出流等
3. 异步处理优化
示例中使用`_ = HandleRequestAsync(context)`启动非阻塞处理,避免阻塞监听循环。实际生产环境中可能需要:
- 限制并发请求数
- 添加请求超时控制
- 使用`SemaphoreSlim`进行流量控制
4. 内容类型处理
示例演示了三种内容类型:
- HTML(`text/html`)
- JSON(`application/json`)
- 错误响应(自定义JSON结构)
四、扩展功能建议
1. 路由系统
当前实现是硬编码处理,可扩展为基于路径的路由:
private Dictionary> _routes =
new Dictionary>
{
["/api/data"] = HandleApiRequest,
["/"] = HandleRootRequest
};
2. 中间件支持
模拟ASP.NET Core的中间件管道:
public class MiddlewarePipeline
{
private readonly List, Task>> _middlewares = new();
public void Use(Func, Task> middleware)
{
_middlewares.Add(middleware);
}
public async Task Invoke(HttpListenerContext context)
{
var index = 0;
async Task Next()
{
if (index
3. 静态文件服务
添加静态文件支持(需处理MIME类型映射):
private async Task ServeStaticFile(HttpListenerContext context, string filePath)
{
if (!File.Exists(filePath))
{
SendErrorResponse(context.Response, HttpStatusCode.NotFound, "File not found");
return;
}
var extension = Path.GetExtension(filePath).ToLower();
var mimeType = extension switch
{
".html" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
_ => "application/octet-stream"
};
var fileBytes = await File.ReadAllBytesAsync(filePath);
context.Response.ContentType = mimeType;
context.Response.ContentLength64 = fileBytes.Length;
await context.Response.OutputStream.WriteAsync(fileBytes, 0, fileBytes.Length);
}
五、性能优化方向
1. 连接复用:默认情况下`HttpListener`会为每个请求创建新连接,可通过`Keep-Alive`头部优化
2. 缓冲区管理:重用`byte[]`缓冲区减少GC压力
3. 异步IO优化:使用`PipeReader`/`PipeWriter`(.NET Core 3.0+)处理流数据
4. 压缩支持:动态压缩响应内容(需处理`Accept-Encoding`头部)
六、安全注意事项
1. 输入验证:对所有用户输入进行严格验证(特别是POST数据)
2. HTTPS支持:通过`netsh http add sslcert`配置SSL证书
3. 请求大小限制:防止内存耗尽攻击
4. CORS策略:明确设置`Access-Control-Allow-Origin`头部
七、与ASP.NET Core对比
特性 | HttpListener | ASP.NET Core |
---|---|---|
依赖项 | 仅System.Net | 需要完整框架 |
性能 | 较低(无中间件优化) | 高性能(Kestrel优化) |
功能 | 基础HTTP处理 | 路由、依赖注入、MVC等 |
适用场景 | 工具开发、测试、嵌入式 | 生产级Web应用 |
八、完整示例改进版
以下是添加路由和静态文件支持的增强版:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
public class EnhancedHttpServer
{
private readonly HttpListener _listener = new HttpListener();
private readonly Dictionary> _routes =
new Dictionary>(StringComparer.OrdinalIgnoreCase);
private readonly string _staticFilesPath;
public EnhancedHttpServer(string urlPrefix = "http://localhost:8080/", string staticFilesPath = null)
{
_listener.Prefixes.Add(urlPrefix);
_staticFilesPath = staticFilesPath;
// 默认路由
_routes["/"] = HandleRoot;
_routes["/api/echo"] = HandleEchoApi;
}
public void AddRoute(string path, Func handler)
{
_routes[path] = handler;
}
public async Task StartAsync()
{
_listener.Start();
Console.WriteLine($"Server started at {_listener.Prefixes[0]}");
try
{
while (true)
{
var context = await _listener.GetContextAsync();
_ = ProcessRequestAsync(context);
}
}
catch (HttpListenerException ex)
{
Console.WriteLine($"Listener error: {ex.Message}");
}
finally
{
_listener.Stop();
}
}
private async Task ProcessRequestAsync(HttpListenerContext context)
{
var request = context.Request;
var response = context.Response;
try
{
// 检查静态文件
if (_staticFilesPath != null && request.HttpMethod == "GET")
{
var filePath = Path.Combine(_staticFilesPath, request.Url.AbsolutePath.TrimStart('/'));
if (File.Exists(filePath))
{
await ServeStaticFile(context, filePath);
return;
}
}
// 检查路由
if (_routes.TryGetValue(request.Url.AbsolutePath, out var handler))
{
await handler(request, response);
}
else
{
SendErrorResponse(response, HttpStatusCode.NotFound, "Resource not found");
}
}
catch (Exception ex)
{
SendErrorResponse(response, HttpStatusCode.InternalServerError, ex.Message);
}
finally
{
response.Close();
}
}
// 路由处理程序示例
private async Task HandleRoot(HttpListenerRequest request, HttpListenerResponse response)
{
var html = @"
Enhanced Server
Enhanced HTTP Server
";
await SendResponseAsync(response, "text/html", html);
}
private async Task HandleEchoApi(HttpListenerRequest request, HttpListenerResponse response)
{
if (request.HttpMethod == "POST")
{
using var reader = new StreamReader(request.InputStream, request.ContentEncoding);
var body = await reader.ReadToEndAsync();
await SendResponseAsync(response, "application/json", $"{{\"echo\":\"{body}\"}}");
}
else
{
SendErrorResponse(response, HttpStatusCode.MethodNotAllowed, "POST only");
}
}
private async Task ServeStaticFile(HttpListenerContext context, string filePath)
{
try
{
var extension = Path.GetExtension(filePath).ToLower();
var mimeType = extension switch
{
".html" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
_ => "application/octet-stream"
};
var fileBytes = await File.ReadAllBytesAsync(filePath);
context.Response.ContentType = mimeType;
context.Response.ContentLength64 = fileBytes.Length;
await context.Response.OutputStream.WriteAsync(fileBytes, 0, fileBytes.Length);
}
catch
{
SendErrorResponse(context.Response, HttpStatusCode.NotFound, "File not found");
}
}
private async Task SendResponseAsync(HttpListenerResponse response, string contentType, string content)
{
var buffer = Encoding.UTF8.GetBytes(content);
response.ContentType = contentType;
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
}
private void SendErrorResponse(HttpListenerResponse response, HttpStatusCode statusCode, string message)
{
var errorResponse = $"{{\"error\":\"{message}\",\"code\":{(int)statusCode}}}";
var buffer = Encoding.UTF8.GetBytes(errorResponse);
response.StatusCode = (int)statusCode;
response.ContentType = "application/json";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
public void Stop()
{
_listener.Stop();
_listener.Close();
}
}
// 使用示例
class Program
{
static async Task Main(string[] args)
{
// 创建带静态文件目录的服务器
var server = new EnhancedHttpServer(
urlPrefix: "http://localhost:8080/",
staticFilesPath: "./wwwroot");
// 添加自定义路由
server.AddRoute("/api/time", async (req, res) =>
{
await server.SendResponseAsync(res, "application/json",
$"{{\"time\":\"{DateTime.Now:HH:mm:ss}\"}}");
});
var startTask = server.StartAsync();
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
server.Stop();
await startTask;
}
}
关键词:C#、.NET、HTTP服务器、HttpListener、异步编程、REST API、静态文件服务、路由系统
简介:本文详细介绍了如何使用C#的HttpListener类实现一个功能完整的HTTP服务器,包含GET/POST请求处理、JSON响应、静态文件服务、路由系统等核心功能,并对比了与ASP.NET Core的差异,适合需要轻量级HTTP服务的开发场景。