搜索结果

×

搜索结果将在这里显示。

🍉使用示例(VB.NET)

安装方式 参考 快速入门

默认端口8090,传入参数则监听指定端口,Windows下注意赋予管理员权限

推荐使用 PicoServer 最新版本,保障功能完整性

由浅入深,完整的示例代码,直接复制粘贴即可运行并验证

1.最简 WebAPI

Private ReadOnly MyAPI As New WebAPIServer() '实例化PicoServer

Sub Main()
    MyAPI.AddRoute("/", AddressOf Hello) '添加根路由映射
    MyAPI.StartServer()
    Console.WriteLine("http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer() '停止服务
End Sub

'根路由映射的方法
Private Async Function Hello(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Await response.WriteAsync("Hello PicoServer")
End Function

相关方法

MyAPI.AddRoute("/", AddressOf Hello) '路由映射,不限制请求方法
MyAPI.AddRoute("/", AddressOf Hello, "GET") '路由映射,限定为 GET 方法

' 🚀 1.7+ 语义化路由 - 开发更爽,复杂应用so easy
MyAPI.MapGet("/", AddressOf Hello)    ' GET
MyAPI.MapPost("/", AddressOf Hello)   ' POST
MyAPI.MapPut("/", AddressOf Hello)    ' PUT
MyAPI.MapDelete("/", AddressOf Hello) ' DELETE

MyAPI.StartServer() '开启服务
MyAPI.StartServer(8891) '开启服务,指定端口
MyAPI.StartServer("127.0.0.1") '开启服务,限定为本机可访问(Pro版)
MyAPI.StartServer("127.0.0.1",8891) '开启服务,限定为本机可访问并指定端口(Pro版)
MyAPI.StopServer()  '停止服务
response.WriteAsync("Hello PicoServer") '写入文本响应(UTF-8编码)
request.HttpMethod '获取请求方法,可根据此实现同一个路由对不同请求方法进行不同处理

2.路由和参数解析

路由有四种风格。

  1. 精准路由
  2. 星号路由
  3. RESTful 风格路由(文档后面)
  4. 特性路由(Pro版)
Private Shared ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    ' 注册精确路由(优先级高于通配符路由)
    MyAPI.AddRoute("/api/user/query", AddressOf QueryUser, "GET")
    MyAPI.AddRoute("/api/user/save", AddressOf SaveUser, "POST")
    MyAPI.AddRoute("/api/user/json", AddressOf SaveUserJson, "POST")

    ' 注册星号通配符路由(每段 URL 仅支持一个 *,支持多段通配)
    MyAPI.AddRoute("/api/*/posts", AddressOf HandleWildcardPost, "POST")
    MyAPI.AddRoute("/api/*/user/*/detail", AddressOf HandleMultiWildcard, "GET")

    ' 启动服务
    MyAPI.StartServer()
    Console.WriteLine("服务已启动 http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

'处理 GET 查询参数请求
'路由:/api/user/query
Private Shared Async Function QueryUser(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim name As String = request.GetQuery("name")
    Dim age As Integer = request.GetQuery(Of Integer)("age")
    Dim isVip As Boolean = request.GetQuery(Of Boolean)("isVip")

    Dim output As String = $"{{
        ""code"": 1,
        ""msg"": ""参数解析成功"",
        ""data"": {{ 
            ""name"": ""{name}"", 
            ""age"": {age}, 
            ""isVip"": {isVip.ToString().ToLower()} 
        }}
    }}"
    Await response.WriteAsync(output)
End Function

'处理 POST 表单数据请求
'路由:/api/user/save
'Content-Type: application/x-www-form-urlencoded
Private Shared Async Function SaveUser(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim formData = request.ParseForm()
    Dim userName As String = formData("userName")
    Dim phone As String = formData("phone")
    Await response.WriteAsync($"{{""code"":1, ""msg"":""表单保存成功"",""data"":{{""userName"":""{userName}"",""phone"":""{phone}""}}}}")
End Function

'处理 POST JSON 数据请求
'路由: /api/user/json
'Content-Type: application/json
Private Shared Async Function SaveUserJson(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim bodyJson As String = Await request.ReadBodyAsStringAsync()
    Await response.WriteAsync($"{{""code"":1, ""msg"":""JSON 保存成功"",""data"":{bodyJson}}}")
End Function

'处理单层星号通配符 POST 请求
'路由:POST /api/*/posts(匹配 /api/xxx/posts,* 为任意单段路径)
'内置防目录遍历,自动拦截 ../ 等非法字符
Private Shared Async Function HandleWildcardPost(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim requestUrl As String = request.Url.AbsolutePath
    Dim bodyJson As String = Await request.ReadBodyAsStringAsync()
    Dim output As String = $"{{
        ""code"": 1,
        ""msg"": ""通配符路由匹配成功"",
        ""data"": {{
            ""requestUrl"": ""{requestUrl}"",
            ""receivedData"": {bodyJson}
        }}
    }}"
    Await response.WriteAsync(output)
End Function

'处理多层星号通配符 GET 请求
' 路由:GET /api/*/user/*/detail(匹配 /api/xxx/user/yyy/detail,每段一个 *)
Private Shared Async Function HandleMultiWildcard(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim requestUrl As String = request.Url.AbsolutePath
    Dim output As String = $"{{""code"":1,""msg"":""多层通配符匹配成功"",""requestUrl"":""{requestUrl}""}}"
    Await response.WriteAsync(output)
End Function

相关方法

request.GetQuery() '获取指定查询参数,不存在则返回null
request.GetQuery(Of T)() '按类型返回指定查询参数,失败返回类型默认值
request.ParseForm() '获取表单数据
request.ReadBodyAsStringAsync() '获取 body 字符串
request.Items() '获取当前请求的扩展属性字典。用于在不同中间件之间传递和共享数据。【Pro版】

3.Cookie 增删改查

Sub Main()
    MyAPI.AddRoute("/cookie/set", AddressOf SetCookie, "GET")
    MyAPI.AddRoute("/cookie/get", AddressOf GetCookie, "GET")
    MyAPI.AddRoute("/cookie/delete", AddressOf DeleteCookie, "GET")
    MyAPI.AddRoute("/cookie/clear", AddressOf ClearCookies, "GET")

    MyAPI.StartServer(8090)
    Console.WriteLine("Cookie 测试服务已启动:http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' 设置 Cookie(支持过期时间、路径、HttpOnly)
Private Async Function SetCookie(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    ' 添加普通 Cookie(1小时过期)
    response.AppendCookie("token", "pico_1234567", New CookieOptions With 
    {
            .Expires = DateTimeOffset.Now.AddHours(1),
            .Path = "/",
            .HttpOnly = True ' 防止前端 JS 读取,提升安全性
    })
    ' 添加自定义路径 Cookie
    response.AppendCookie("theme", "dark", New CookieOptions With 
    {
            .Expires = DateTimeOffset.Now.AddDays(7),
            .Path = "/"
    })
    response.BuildCookie() '关键,多个Cookie需进行拼接
    Await response.WriteAsync("{""code"":1, ""msg"":""Cookie 设置成功""}")
End Function

' 读取 Cookie
Private Async Function GetCookie(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    ' 安全读取 Cookie(避免空引用)
    Dim token As String = Nothing
    request.TryGetCookieValue("token", token)
    Dim theme As String = Nothing
    request.TryGetCookieValue("theme", theme)

    Await response.WriteAsync($$"""{{""code"":1, ""token"":""{token}"",""theme"":""{theme}""}}")
End Function

' 删除指定 Cookie
Private Async Function DeleteCookie(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    response.DeleteCookie("token", New CookieOptions With {.Path = "/"})
    Await response.WriteAsync("{""code"":1, ""msg"":""Token Cookie 删除成功""}")
End Function

' 批量清理所有 Cookie
Private Async Function ClearCookies(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    response.ClearCookies()
    Await response.WriteAsync("{""code"":1, ""msg"":""所有 Cookie 已清理""}")
End Function

4.RESTful 风格路由

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.AddRoute("/users", AddressOf Users)
    MyAPI.StartServer()
    Console.WriteLine("服务已启动:http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

Private Async Function Users(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    response.ContentType = GetContentType(".json")
    Select Case request.HttpMethod
        Case "GET"
            Await response.WriteAsync("{""code"":1,""msg"":""获取用户成功"",""data"":{""username"":""PicoServer""}}")
        Case "POST"
            response.StatusCode = 201
            Await response.WriteAsync("{""code"":1,""msg"":""创建用户成功"",""data"":{""userId"":""123456""}}")
        Case Else
            response.StatusCode = 405
            Await response.WriteAsync("{""code"":0,""msg"":""方法不允许""}")
    End Select
End Function

相关方法

GetContentType(".html") '根据文件扩展名获取 MIME 类型:text/html;charset=UTF-8

GetContentType 为跨平台通用方法(Windows/Linux/Docker 结果一致),可根据文件扩展名获取标准化 MIME 类型,支持的扩展名有:.html/.htm、.css、.js、.json、.txt、.xml、.jpg/.jpeg、.png、.gif、.webp、.svg/.svgz、.ico、.mp4、.webm、.mp3、.wav、.m3u8、.ts、.woff2、.woff、.ttf、.otf、.pdf、.zip、.rar、.7z、.apk;文本类类型默认带 UTF-8 编码,未知类型返回 application/octet-stream。

5.特性路由

标记特性即可自动注册路由,构建高级应用更爽快。免费版完全支持 AOT ,但特性路由通过扩展包PicoServer.Extensions反射实现,Pro 版特性路由支持 AOT

<ApiController>
Public Class UserController
    <ApiRoute("/api/user", "GET")>
    Public Async Function GetUser(req As HttpListenerRequest, res As HttpListenerResponse) As Task
        Await res.WriteAsync("{""id"":1,""name"":""张三""}", WebAPIServer.ContentType.ApplicationJson)
    End Function

    <ApiRoute("/api/user", "POST")>
    Public Async Function CreateUser(req As HttpListenerRequest, res As HttpListenerResponse) As Task
        Await res.WriteAsync("{""success"":true}", WebAPIServer.ContentType.ApplicationJson)
    End Function
End Class

相关函数

MyAPI.AutoRegisterRoutes() '自动注册特性路由

6.静态文件托管

静态文件(HTML/CSS/JS/ 图片 / 视频)托管配置,适配前端页面直接访问、静态资源服务等场景。如B/S架构的网站应用

Friend ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    '添加静态文件服务, wwwroot 目录
    MyAPI.AddStaticFiles("/", "wwwroot")
    MyAPI.AddCors() '允许跨域
    MyAPI.StartServer()
    '保持程序作为服务运行,兼容windows和linux
    Console.WriteLine("服务器已启动,按Ctrl+C退出...")
    Thread.Sleep(Timeout.Infinite)
End Sub

相关函数

'添加静态文件服务。第一个参数为路由,第二个为服务端文件夹(相对/绝对路径),没有匹配到文件则回到路由匹配
MyAPI.AddStaticFiles("/", "wwwroot") '默认开启自动识别缓存
app.AddStaticFiles("/web", "www", 2592000)  ' 缓存30 天
MyAPI.AddCors() '启用跨域

7.跨域配置

解决前后端分离跨域限制,支持极简配置与自定义配置

MyAPI.AddCors() '启用跨域,默认允许所有来源/方法/请求头
MyAPI.AddCors("picoserver.cn") '支持指定跨域
'更多个性化跨域自定义跨域中间件即可。

8.文件上传/下载

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.AddRoute("/file/download", AddressOf DownloadFile, "GET")    ' 文件下载/预览
    MyAPI.AddRoute("/file/upload", AddressOf UploadFile, "POST")       ' 文件上传

    MyAPI.StartServer()
    Console.WriteLine("文件服务已启动:http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' 文件下载/预览(asAttachment=false 预览,true 强制下载)
Private Async Function DownloadFile(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim TestFile As String = "D:\test.mp4"
    If Not File.Exists(TestFile) Then
        Await response.WriteAsync("{""code"":0, ""msg"":""文件不存在""}")
        Return
    End If
    Await response.SendFileAsync(TestFile, True)
End Function

' 文件上传(带进度回调)
Private Async Function UploadFile(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim fname As String = request.Headers("filename") '从请求头中获取文件名(test112.mp4),示例为相对位置,中文文件名建议用BASE64编码或者URL编码
    Dim isSuccess As Boolean = Await request.SaveFileAsync(fname, Sub(current, total)
        ' 打印上传进度(百分比)
        Console.WriteLine($"上传进度:{ current * 100 / total}%")
    End Sub)

    If isSuccess Then
        Await response.WriteAsync("{""code"":1, ""msg"":""文件上传成功""}")
    Else
        Await response.WriteAsync("{""code"":0, ""msg"":""文件上传失败""}")
    End If
    Console.WriteLine($"请求头中的文件名:{fname}")
End Function

相关函数

'发送文件,支持断点下载
response.SendFileAsync() '流式发送,大文件低内存消耗,根据扩展名自动添加文件类型
response.SendFileAsync(filePath) '支持文档/视频等直接预览
response.SendFileAsync(filePath,True) '强制下载
response.SendFileAsync(Mp4Path,False,request) '播放视频,支持拖动播放
'接受文件上传,支持断点续传
request.SaveFileAsync() '流式保存,大文件低内存消耗
request.SaveFileAsync(filePath) '保存文件到指定路径(相对/绝对皆可),需要包含文件名
request.SaveFileAsync(filePath,AddressOf onProgress) '保存文件到指定路径,支持回调进度。

request.Headers("filename") '举例:从请求头中获取文件名,生产中应进行非空判断

9.流媒体/直播流推送

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.AddRoute("/stream/live", AddressOf LiveStream, "GET") ' 直播流推送
    MyAPI.StartServer()
    Console.WriteLine("流媒体服务已启动:http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

Private Async Function LiveStream(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim LiveFile As String = "D:\test.mp4" ' 直播源文件(可替换为实时流)
    If Not File.Exists(LiveFile) Then
        Await response.WriteAsync("{""code"":0, ""msg"":""直播源不存在""}")
        Return
    End If

    ' 打开文件流(FileShare.ReadWrite 允许文件被其他程序写入)
    Using fs As New FileStream(LiveFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
        Await response.SendStreamAsync(fs, GetContentType(".mp4"), True)
    End Using
End Function

相关方法

response.SendStreamAsync(fs, GetContentType(".mp4"), True) '无缓存、Chunked 传输,适配实时流,各种流

10.简单 Token/JWT 鉴权

一旦添加鉴权,默认所有路由都需要鉴权,不需要鉴权的路由需要添加到路由白名单

白名单只针对精准路由(路径),不支持星号路由

路由白名单

MyAPI.RouteWhiteList '储存路由白名单的集合
MyAPI.RouteWhiteList.Add("/api/login") '添加接口到白名单,无需验证

简单token鉴权

Private ReadOnly MyAPI As New WebAPIServer()
Private _testToken As String = "PicoServer123" ' 测试用 Token

Sub Main()
    MyAPI.AddRoute("/api/login", AddressOf Login, "POST")
    MyAPI.AddRoute("/api/user/info", AddressOf GetUserInfo, "GET")

    '配置白名单(放行登录接口)
    MyAPI.RouteWhiteList.Add("/api/login")
    MyAPI.AddSimpleTokenVerify(_testToken)

    MyAPI.StartServer()
    Console.WriteLine($"鉴权服务已启动,测试 Token:{_testToken}")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' 登录接口(白名单,无需鉴权)
Private Async Function Login(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Await response.WriteAsync($"{{""code"":1, ""msg"":""登录成功"",""token"":""{_testToken}""}}")
End Function

' 用户信息接口(需鉴权,非白名单)
Private Async Function GetUserInfo(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Dim token As String = request.GetToken()
    Await response.WriteAsync($"{{""code"":1, ""msg"":""获取信息成功"",""token"":{token}}}")
End Function

相关方法

MyAPI.AddSimpleTokenVerify(testToken) '添加简单 token 验证中间件,参数为token
request.GetToken() '获取请求头中的token值

JWT鉴权

Private ReadOnly MyAPI As New WebAPIServer()
Private _testToken As String ' 测试用 Token

Sub Main()
    ' 1. 注册路由(先注册,后配置鉴权)
    MyAPI.AddRoute("/api/login", AddressOf Login, "POST")       ' 白名单路由(无需鉴权)
    MyAPI.AddRoute("/api/user/info", AddressOf GetUserInfo, "GET") ' 需鉴权路由

    ' 2. 配置白名单(放行登录接口)
    MyAPI.RouteWhiteList.Add("/api/login")

    ' 3. 启用 JWT 鉴权(密钥需与生成 Token 时一致)
    MyAPI.AddJwtTokenVerify("pico_secret_779")

    ' 4. 生成测试 Token(模拟登录成功)
    GenerateTestToken()

    MyAPI.StartServer()
    Console.WriteLine($"鉴权服务已启动,测试 Token:{_testToken}")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' 简化版:生成测试 JWT Token
Private Sub GenerateTestToken()
    ' 过期时间:当前+1小时(10位时间戳)
    Dim exp As Long = MyAPI.GetTimeStamp10(3600)
    ' 直接使用字符串插值构造载荷,简化代码
    Dim payload As String = $"{{""username"":""admin"",""role"":""super"",""exp"":{exp}}}"
    ' 生成 Token
    _testToken = MyAPI.Jwt.GenerateToken(payload)
End Sub

' 登录接口(白名单,无需鉴权)
Private Async Function Login(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    ' 模拟登录验证(实际场景替换为账号密码校验)
    Await response.WriteAsync($"{{""code"":1, ""msg"":""登录成功"",""token"":""{_testToken}""}}")
End Function

' 用户信息接口(需鉴权,非白名单)
Private Async Function GetUserInfo(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    ' 获取请求头中的 Token 并解析载荷
    Dim token As String = request.GetToken()
    Dim payload As String = MyAPI.Jwt.DecodePayload(token)

    Await response.WriteAsync($"{{""code"":1, ""msg"":""获取信息成功"",""userInfo"":{payload}}}")
End Function

相关方法

PicoServer.Crypto(Pro版)

MyAPI.AddJwtTokenVerify("pico_secret_779") '添加 JWT 鉴权中间件,参数为HS256加密密钥
MyAPI.AddJwtTokenVerify("pico_secret_779", PicoServer.Crypto.HashType.SM3) '添加 JWT 鉴权中间件,参数为 HMAC-SM3 加密密钥

request.GetToken() '获取请求头中的token值
MyAPI.Jwt.DecodePayload(token) '解码 JWT 负载为字符串
MyAPI.Jwt.GenerateToken(payload) '创建 JWT token 参数为负载,用于储存信息

MyAPI.GetTimeStamp10(3600) ' 获取10位时间戳,参数:需要追加的秒数,不传入则返回当前时间戳
MyAPI.GetTimeStamp13() ' 获取13位时间戳,参数:需要追加的毫秒数,不传入则返回当前时间戳

' 加密工具(PicoServer.Crypto)
Dim signature As String = HS256.ComputeHmac256(data, key) ' HMAC-SHA256 签名
Dim hash As Byte() = SM3.ComputeHash(data) ' SM3 哈希计算
Dim hmacSm3 As String = SM3.ComputeHmacSM3(data, key) ' HMAC-SM3 签名
Dim passwordHash As String = SM3.HashPassword(password, salt, iterations) ' SM3 密码哈希
Dim encoded As String = Base64Url.Encode(data) ' Base64Url 编码
Dim decoded As String = Base64Url.Decode(encoded) ' Base64Url 解码

11.SSE服务端推送

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.AddRoute("/iot/notify", AddressOf Notify, "GET") ' SSE推送
    MyAPI.StartServer(8090)
    Console.WriteLine("SSE推送服务已启动:http://127.0.0.1:8090/iot/notify")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' SSE推送消息推送(模拟设备报警)
Public Shared Async Function Notify(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    response.ContentType = "text/event-stream"
    response.Headers.Add("Cache-Control", "no-cache")
    response.SendChunked = True

    Try
        For i As Integer = 0 To 4
            Dim msg As String = $"data: 消息推送 {i} 时间: {DateTime.Now}{vbCrLf}{vbCrLf}"
            Dim buffer As Byte() = System.Text.Encoding.UTF8.GetBytes(msg)
            Await response.OutputStream.WriteAsync(buffer, 0, buffer.Length)
            Await response.OutputStream.FlushAsync()
            Await Task.Delay(1000)
        Next
    Finally
        '在使用 HttpListenerResponse 进行 SSE(Server - Sent Events)推送时,response.Close(); 并不是必须的,但推荐在推送结束后调用它,以确保资源释放和连接正确关闭。
        ' 示例这里是推送结束后调用 response.Close();,确保响应流关闭
        ' 如果是无限推送(如实时设备报警),不要关闭响应,直到客户端断开。
        response.Close()
    End Try
End Function

前端 JavaScript 示例(如果测试时候不能访问,注意开启跨域)

function startSSE() {
    const source = new EventSource("/iot/notify");
    source.onmessage = function (event) {
        document.getElementById("result").innerHTML += event.data + "<br>";
    };
    source.onerror = function () {
        source.close();
    };
}

PicoServer SSE 在线测试工具 https://picoserver.cn/tools/ssetest.html

12.长连接消息推送

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.AddRoute("/notify", AddressOf LongConnectionPush, "GET") ' 长连接推送
    MyAPI.StartServer(8090)
    Console.WriteLine("长连接服务已启动:http://127.0.0.1:8090/notify")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

' 长连接消息推送(模拟设备报警)
Private Async Function LongConnectionPush(request As HttpListenerRequest, response As HttpListenerResponse) As Task
    Try
        ' 循环推送消息(实际场景替换为业务事件触发)
        For i As Integer = 0 To 29
            ' 推送报警消息(低内存)
            Await response.WriteChunkAsync($"设备报警 {i}:温度异常 {DateTime.Now:HH:mm:ss}" & vbCrLf)
            ' 模拟 1 秒推送一次
            Await Task.Delay(1000)
        Next

        ' 推送结束,发送结束标识
        Await response.WriteChunkAsync("推送结束")
    Finally
        ' 示例这里是推送结束后调用 response.Close();,确保响应流关闭
        ' 如果是无限推送(如实时设备报警),不要关闭响应,直到客户端断开。
        response.Close()
    End Try
End Function

相关方法

response.WriteChunkAsync("推送结束") '推送字符串
response.Close() '关闭响应,释放资源 【WriteChunkAsync下必须手动关闭,避免资源泄露】

13.WebSocket 通信

  • 场景目标:实现 WebSocket 双向交互,适配实时聊天、设备指令交互等场景

  • 核心要点:服务端(enableWebSocket 启用、事件订阅、在线客户端管理、广播消息)、客户端(WebSocketClient 初始化、事件订阅、消息发送)

  • 运行测试:服务端启动、客户端连接、双向消息交互、广播消息接收

WebSocket 服务端可以和 WebAPI 同时存在,且共用端口,共用地址
WebSocket 在线测试工具 https://wstool.js.org/

WebSocket 服务端

Private ReadOnly MyAPI As New WebAPIServer()

Sub Main()
    MyAPI.enableWebSocket = True
    MyAPI.WsOnConnectionChanged = AddressOf WsConnectChanged
    MyAPI.WsOnMessage = AddressOf OnMessageReceived

    MyAPI.StartServer()
    Console.WriteLine("PicoServer WebSocket:http://127.0.0.1:8090")
    Console.ReadKey()
    MyAPI.StopServer()
End Sub

Private Async Function WsConnectChanged(clientId As String, connected As Boolean) As Task
    Await MyAPI.WsBroadcastAsync($"{clientId} {connected}")
End Function

Private Async Function OnMessageReceived(clientId As String, message As String, reply As Func(Of String, Task)) As Task
    Await reply("收到!")
    Dim clients = MyAPI.WsGetOnlineClients()
    For Each client In clients
        Await MyAPI.WsSendToClientAsync(client, $"{clientId}说:{message}")
    Next
End Function

相关方法

MyAPI.enableWebSocket = True '启用WebSocket支持
MyAPI.WsOnConnectionChanged ' 事件:WebSocket客户端连接状态发生变化
MyAPI.WsOnMessage '事件:收到WebSocket客户端发送来的消息
MyAPI.WsBroadcastAsync() '对所有在线客户端广播消息
MyAPI.WsGetOnlineClients '获取在线客户端列表
MyAPI.WsSendToClientAsync(client,message) '给指定客户端发送消息
MyAPI.WsEnableHeartbeat = True '启用 WebSocket 服务端心跳检测,默认false
MyAPI.WsHeartbeatTimeout = 60 '设置 WebSocket 心跳时间,默认30秒
MyAPI.WsMaxConnections = 200 '设置 WebSocket 最大连接数,默认100
MyAPI.WsPingString = "hi" '设置 WebSocket 的ping消息,默认"ping",不区分大小写

WebSocket 客户端

Private wsClient As New WebSocketClient("wss://echo.websocket.org/")

Sub Main()
    ' 订阅核心事件
    AddHandler wsClient.OnConnected, AddressOf OnConnected ' 连接成功
    AddHandler wsClient.OnMessageReceived, AddressOf OnMessageReceived ' 接收消息
    AddHandler wsClient.OnDisconnected, AddressOf OnDisconnected ' 连接断开
    AddHandler wsClient.OnError, AddressOf OnError ' 异常触发
    wsClient.StartConnect()
    Console.ReadKey()
    wsClient.StopConnect()
End Sub

Private Sub OnConnected(sender As Object, e As EventArgs)
    Console.WriteLine("连接成功!")
End Sub

Private Sub OnMessageReceived(sender As Object, message As String)
    Console.WriteLine($"收到消息: {message}")
    wsClient.SendMessageAsync($"hi: {DateTime.Now.ToShortTimeString()}")
End Sub

Private Sub OnDisconnected(sender As Object, e As EventArgs)
    Console.WriteLine("连接已断开!")
End Sub

Private Sub OnError(sender As Object, e As WebSocketErrorEventArgs)
    Console.WriteLine($"错误: {e.ErrorCode}, {e.ErrorMessage}")
End Sub

相关方法

Private wsClient As New WebSocketClient() '实例化WebSocket客户端
Private wsClient As New WebSocketClient("wss://echo.websocket.org/") '实例化WebSocket服务端,支持ws和wss,默认5秒超时
Private wsClient As New WebSocketClient("wss://echo.websocket.org/",10) '自定义超时时间10秒

wsClient.StartConnect() '连接服务端
wsClient.StopConnect()  '断开连接
wsClient.SendMessageAsync(message) '发送消息
wsClient.SendPingAsync() '发送ping消息,默认“ping”
wsClient.SendPingAsync("hi") '发送自定义ping消息

14.高级用法和中间件

PicoServer 开放了 中间件 ,可以借此进行二次开发、封装、集成解决方案等 。

利用 AddMiddleware() 可开发属于自己的中间件。比如:参数路由,限流,黑名单等等。

极简文件服务器

Imports PicoServer

Dim MyAPI = New WebAPIServer()
Dim rootPath As String = "D:\MyFiles" ' 设定你的物理资源根目录

' 1. 首页:列出所有文件 (简单 WebAPI)
MyAPI.AddRoute("/files", Async Function(req, resp)
    Dim files = Directory.GetFiles(rootPath).Select(Function(f) Path.GetFileName(f))
    Dim html = $"
        <h1>文件清单</h1>
        <ul>
            {String.Join("", files.Select(Function(f) $"<li><a href='/view?name={f}'>{f}</a></li>"))}
        </ul>
        "
    Await resp.WriteAsync(html, WebAPIServer.ContentType.TextHtml)
End Function, "GET")

' 2. 查看/预览:支持视频拖动播放 (利用 asAttachment: false)
MyAPI.AddRoute("/view", Function(req, resp)
    Dim fileName As String = req.ParseGetQueryString("name")
    Dim fullPath As String = Path.Combine(rootPath, fileName)

    ' asAttachment: false 允许浏览器直接播放视频或预览图片
    Return resp.SendFileAsync(fullPath, asAttachment:=False)
End Function, "GET")

' 3. 强制下载:(默认 asAttachment: true)
MyAPI.AddRoute("/download", Function(req, resp)
    Dim fileName As String = req.ParseGetQueryString("name")
    Return resp.SendFileAsync(Path.Combine(rootPath, fileName))
End Function, "GET")

MyAPI.StartServer(8090)
Console.WriteLine("文件服务器已启动: http://127.0.0.1:8090")
Console.ReadKey()

自定义中间件

这个示例演示了如何利用自定义中间件的扩展功能实现动态路由,在一个逻辑块内完成路径匹配方法分发请求终结,从而灵活的自定义各种中间件。注意:中间件的执行是按添加顺序执行的

极简动态路由

Imports PicoServer
Imports System.Text.RegularExpressions

Dim MyAPI = New WebAPIServer()

' 【提示】前置鉴权/日志中间件应在此处注册

' 极简动态路由:建议作为最后的处理器
MyAPI.AddMiddleware(Async Function(req, resp)
    ' 匹配 /product/123 这种带 ID 的路径
    Dim match = Regex.Match(req.Url.AbsolutePath, "^/product/(?<id>\d+)$")

    If match.Success Then
        Dim id As String = match.Groups("id").Value

        ' 根据请求方式 (Method) 分发逻辑
        Select Case req.HttpMethod
            Case "GET"
                Await resp.WriteAsync($$"""{"action":"查询商品", "id":{{id}}}""")
            Case "POST"
                Await resp.WriteAsync($$"""{"action":"更新商品", "id":{{id}}, "result":"Success"}""")
            Case Else
                resp.StatusCode = 405
                Await resp.WriteAsync("""{"msg":"不支持的操作"}""")
        End Select

        Return False ' 【核心】匹配成功并处理,返回 false 终结请求,不再走默认路由
    End If

    Return True ' 没匹配上,放行给后续逻辑或 404,如果是自定义路由建议放在最后,且只返回false,不再继续分发。
End Function)

MyAPI.StartServer(8090)
Console.WriteLine("动态路由服务已启动: http://127.0.0.1:8090")
Console.ReadKey()
发布时间: